From 147148ab1ca5f39f268c738b1d6c1322925b82e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:44:20 +0000 Subject: [PATCH 1/3] Initial plan From 1f415c4c1562e35c7cf0b6caedee16137134a3a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:04:37 +0000 Subject: [PATCH 2/3] Fix ADF pipeline dependency logic to handle array/non-array scopes Co-authored-by: RolandKrummenacher <1803486+RolandKrummenacher@users.noreply.github.com> --- docs-mslearn/toolkit/changelog.md | 1 + docs/deploy/finops-hub-12.0.json | 30948 +++++++++------- docs/deploy/finops-hub-12.0.ui.json | 47 + docs/deploy/finops-hub-latest.json | 30948 +++++++++------- docs/deploy/finops-hub-latest.ui.json | 47 + .../ManagedExports/app.bicep | 3 + 6 files changed, 36452 insertions(+), 25542 deletions(-) diff --git a/docs-mslearn/toolkit/changelog.md b/docs-mslearn/toolkit/changelog.md index db543056d..fcc5c69dd 100644 --- a/docs-mslearn/toolkit/changelog.md +++ b/docs-mslearn/toolkit/changelog.md @@ -49,6 +49,7 @@ _Released August 2025_ - Fixed all Bicep compilation errors and warnings with inline suppressions and descriptive comments. - Fixed Build-Toolkit.ps1 bicep generate-params command bug. - Fixed Azure Data Explorer dashboard queries by converting `todecimal(0)` to `toreal(0)` to ensure compatibility with KQL type system ([#1893](https://github.com/microsoft/finops-toolkit/issues/1893)). + - Fixed ADF pipeline dependency logic in config_RunBackfillJob, config_StartExportProcess, and config_ConfigureExports pipelines to properly handle both array and non-array scope configurations by adding 'Failed' condition to 'Save/Set Scopes' activity dependencies. ### [Optimization engine](optimization-engine/overview.md) v13 diff --git a/docs/deploy/finops-hub-12.0.json b/docs/deploy/finops-hub-12.0.json index 4dd0a7f94..3a65ba896 100644 --- a/docs/deploy/finops-hub-12.0.json +++ b/docs/deploy/finops-hub-12.0.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "1039455044893847676" + "version": "0.39.26.7824", + "templateHash": "8677558940311187779" } }, "parameters": { @@ -233,7 +233,7 @@ "resources": [ { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "hub", "properties": { "expressionEvaluationOptions": { @@ -312,43 +312,29 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "7621563314037220493" + "version": "0.39.26.7824", + "templateHash": "13528846700335310209" } }, "definitions": { "_1.HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -357,25 +343,24 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -461,7 +446,7 @@ "apps": {}, "description": "FinOps hub instance properties.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -497,6 +482,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -523,6 +511,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -530,7 +519,7 @@ }, "description": "FinOps hub private network routing properties.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -549,7 +538,7 @@ "name": "Resource name.", "description": "Resource ID and name.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } } @@ -571,7 +560,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -595,7 +584,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -607,54 +596,38 @@ }, { "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherSuffix" - }, - { - "type": "object", - "name": "publisherTags" + "name": "id" }, { "type": "string", - "name": "appName" + "name": "name" }, { "type": "string", - "name": "appDisplayName" + "name": "publisher" }, { "type": "string", - "name": "version" + "name": "suffix" } ], "output": { "$ref": "#/definitions/_1.HubAppProperties", "value": { - "name": "[parameters('appName')]", - "displayName": "[parameters('appDisplayName')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", - "publisher": { - "name": "[parameters('publisherName')]", - "displayName": "[parameters('publisherDisplayName')]", - "suffix": "[parameters('publisherSuffix')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" - }, + "id": "[parameters('id')]", + "name": "[parameters('name')]", + "publisher": "[parameters('publisher')]", + "suffix": "[parameters('suffix')]", + "tags": "[union(parameters('hub').tags, createObject('ftk-hubapp-publisher', parameters('publisher')))]", "hub": "[parameters('hub')]", - "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" + "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('suffix'))), 1)), parameters('suffix')), '--', '-')]", + "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('suffix'))), 1)), parameters('suffix')), '--', '-')]", + "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('suffix')))), parameters('suffix'))]" } }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -742,6 +715,7 @@ "table": "[if(parameters('enablePublicAccess'), createObject('id', '', 'name', ''), _1.dnsZoneIdName('table'))]" }, "subnets": { + "dataExplorer": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'dataExplorer-subnet'))]", "dataFactory": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'private-endpoint-subnet'))]", "keyVault": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'private-endpoint-subnet'))]", "scripts": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'script-subnet'))]", @@ -755,7 +729,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -772,7 +746,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } } @@ -799,7 +773,7 @@ "metadata": { "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -811,33 +785,21 @@ }, { "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "appPartialName" + "name": "publisher" }, { "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" + "name": "app" } ], "output": { "$ref": "#/definitions/_1.HubAppProperties", - "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" + "value": "[_1.newAppInternal(parameters('hub'), format('{0}.{1}', parameters('publisher'), parameters('app')), parameters('app'), parameters('publisher'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisher'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisher'))))]" }, "metadata": { "description": "Creates a new FinOps hub app configuration object.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -891,7 +853,7 @@ "metadata": { "description": "Creates a new FinOps hub configuration object.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } } @@ -1140,12 +1102,12 @@ }, "variables": { "$fxv#0": "12.0", - "$fxv#1": "12.0", + "$fxv#1": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\n#\n$adfParams = @{\n ResourceGroupName = $env:DataFactoryResourceGroup\n DataFactoryName = $env:DataFactoryName\n}\n\n# Delete old triggers\n$triggers = Get-AzDataFactoryV2Trigger @adfParams -ErrorAction SilentlyContinue `\n| Where-Object { $_.Name -match '^msexports(_(setup|daily|monthly|extract|FileAdded))?$' }\n$DeploymentScriptOutputs[\"stopTriggers\"] = $triggers | Stop-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\n$DeploymentScriptOutputs[\"deleteTriggers\"] = $triggers | Remove-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\n\n# Delete old pipelines\n$DeploymentScriptOutputs[\"pipelines\"] = Get-AzDataFactoryV2Pipeline @adfParams -ErrorAction SilentlyContinue `\n| Where-Object { $_.Name -match '^(msexports_(backfill|extract|fill|get|run|setup|transform)|config_(BackfillData|ExportData|RunBackfill|RunExports))$' } `\n| Remove-AzDataFactoryV2Pipeline -Force -ErrorAction SilentlyContinue\n", "hub": "[__bicep.newHub(parameters('hubName'), parameters('location'), parameters('tags'), parameters('tagsByResource'), parameters('storageSku'), parameters('keyVaultSku'), parameters('enableInfrastructureEncryption'), parameters('enablePublicAccess'), parameters('virtualNetworkAddressPrefix'), parameters('enableDefaultTelemetry'))]", "useFabric": "[not(empty(parameters('fabricQueryUri')))]", - "deployDataExplorer": "[and(not(variables('useFabric')), not(empty(parameters('dataExplorerName'))))]", + "useAzureDataExplorer": "[and(not(variables('useFabric')), not(empty(parameters('dataExplorerName'))))]", "telemetryId": "00f120b5-2007-6120-0000-40b000000000", - "telemetryString": "[join(createArray(if(or(empty(parameters('remoteHubStorageUri')), empty(parameters('remoteHubStorageKey'))), '', 'R'), substring(split(parameters('storageSku'), '_')[1], 0, 1), if(not(variables('useFabric')), '', format('F{0}', parameters('fabricCapacityUnits'))), if(not(variables('deployDataExplorer')), '', format('X{0}', substring(parameters('dataExplorerSku'), 0, 1))), if(not(variables('deployDataExplorer')), '', replace(replace(replace(replace(replace(replace(replace(replace(split(split(parameters('dataExplorerSku'), 'Standard_')[1], '_')[0], 'C', ''), 'D', ''), 'E', ''), 'L', ''), 'a', ''), 'd', ''), 'i', ''), 's', '')), if(or(not(variables('deployDataExplorer')), equals(parameters('dataExplorerCapacity'), 1)), '', format('x{0}', parameters('dataExplorerCapacity'))), if(parameters('enablePublicAccess'), '', 'P')), '')]", + "telemetryString": "[join(createArray(if(or(empty(parameters('remoteHubStorageUri')), empty(parameters('remoteHubStorageKey'))), '', 'R'), substring(split(parameters('storageSku'), '_')[1], 0, 1), if(not(variables('useFabric')), '', format('F{0}', parameters('fabricCapacityUnits'))), if(not(variables('useAzureDataExplorer')), '', format('X{0}', substring(parameters('dataExplorerSku'), 0, 1))), if(not(variables('useAzureDataExplorer')), '', replace(replace(replace(replace(replace(replace(replace(replace(split(split(parameters('dataExplorerSku'), 'Standard_')[1], '_')[0], 'C', ''), 'D', ''), 'E', ''), 'L', ''), 'a', ''), 'd', ''), 'i', ''), 's', '')), if(or(not(variables('useAzureDataExplorer')), equals(parameters('dataExplorerCapacity'), 1)), '', format('x{0}', parameters('dataExplorerCapacity'))), if(parameters('enablePublicAccess'), '', 'P')), '')]", "_1.finOpsToolkitVersion": "12.0" }, "resources": { @@ -1170,18 +1132,33 @@ } } }, - "infrastructure": { + "core": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Infrastructure", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "hub": { - "value": "[variables('hub')]" + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'Core')]" + }, + "scopesToMonitor": { + "value": "[parameters('scopesToMonitor')]" + }, + "msexportRetentionInDays": { + "value": "[parameters('exportRetentionInDays')]" + }, + "ingestionRetentionInMonths": { + "value": "[parameters('ingestionRetentionInMonths')]" + }, + "rawRetentionInDays": { + "value": "[parameters('dataExplorerRawRetentionInDays')]" + }, + "finalRetentionInMonths": { + "value": "[parameters('dataExplorerFinalRetentionInMonths')]" } }, "template": { @@ -1191,11 +1168,97 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "8095489755767003461" + "version": "0.39.26.7824", + "templateHash": "13614615614696914627" } }, "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, "_1.HubRoutingProperties": { "type": "object", "properties": { @@ -1228,6 +1291,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -1254,6 +1320,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -1261,7 +1328,7 @@ }, "description": "FinOps hub private network routing properties.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "../../fx/hub-types.bicep" } } }, @@ -1280,11 +1347,11 @@ "name": "Resource name.", "description": "Resource ID and name.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "../../fx/hub-types.bicep" } } }, - "HubProperties": { + "HubAppProperties": { "type": "object", "properties": { "id": { @@ -1293,1082 +1360,400 @@ "name": { "type": "string" }, - "location": { + "publisher": { "type": "string" }, - "tags": { - "type": "object" + "suffix": { + "type": "string" }, - "tagsByResource": { + "tags": { "type": "object" }, - "version": { + "dataFactory": { "type": "string" }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } + "keyVault": { + "type": "string" }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" + "storage": { + "type": "string" }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "../../fx/hub-types.bicep" } } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getHubTags": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(parameters('hub').tags, coalesce(tryGet(parameters('hub').tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", + "app": { + "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub instance properties." + "description": "Required. FinOps hub app getting deployed." } - } - }, - "variables": { - "nsgName": "[format('{0}-nsg', parameters('hub').routing.networkName)]", - "finopsHubSubnetName": "private-endpoint-subnet", - "scriptSubnetName": "script-subnet", - "dataExplorerSubnetName": "dataExplorer-subnet", - "subnets": "[if(not(parameters('hub').options.privateRouting), createArray(), createArray(createObject('name', variables('finopsHubSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 0), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('scriptSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'delegations', createArray(createObject('name', 'Microsoft.ContainerInstance/containerGroups', 'properties', createObject('serviceName', 'Microsoft.ContainerInstance/containerGroups'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('dataExplorerSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 27, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName')))))))]" - }, - "resources": { - "vNet::finopsHubSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('finopsHubSubnetName'))]", - "dependsOn": [ - "vNet" - ] }, - "vNet::scriptSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('scriptSubnetName'))]", - "dependsOn": [ - "vNet" - ] + "scopesToMonitor": { + "type": "array", + "metadata": { + "description": "Optional. List of scope IDs to monitor and ingest cost for." + } }, - "vNet::dataExplorerSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('dataExplorerSubnetName'))]", - "dependsOn": [ - "vNet" - ] + "msexportRetentionInDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Number of days of data to retain in the msexports container. Default: 0." + } }, - "blobPrivateDnsZone::blobPrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.blob.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.blob.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "blobPrivateDnsZone" - ] + "ingestionRetentionInMonths": { + "type": "int", + "defaultValue": 13, + "metadata": { + "description": "Optional. Number of months of data to retain in the ingestion container. Default: 13." + } }, - "dfsPrivateDnsZone::dfsPrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.dfs.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.dfs.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "dfsPrivateDnsZone" - ] + "rawRetentionInDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." + } }, - "queuePrivateDnsZone::queuePrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.queue.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.queue.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "finalRetentionInMonths": { + "type": "int", + "defaultValue": 13, + "metadata": { + "description": "Optional. Number of months of data to retain in the Data Explorer *_final_v* tables. Default: 13." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nWrite-Output \"Updating settings.json file...\"\nWrite-Output \" Storage account: $env:storageAccountName\"\nWrite-Output \" Container: $env:containerName\"\n\n$validateScopes = { $_.Length -gt 45 }\n\n# Initialize variables\n$fileName = 'settings.json'\n$filePath = Join-Path -Path . -ChildPath $fileName\n$newScopes = $env:scopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Download existing settings, if they exist\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\nif ($blob)\n{\n $text = Get-Content $filePath -Raw\n Write-Output \"---------\"\n Write-Output $text\n Write-Output \"---------\"\n $json = $text | ConvertFrom-Json\n Write-Output \"Existing settings.json file found. Updating...\"\n\n # Rename exportScopes to scopes + convert to object array\n if ($json.exportScopes)\n {\n Write-Output \" Updating exportScopes...\"\n if ($json.exportScopes[0] -is [string])\n {\n Write-Output \" Converting string array to object array...\"\n $json.exportScopes = @($json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } })\n if (-not ($json.exportScopes -is [array]))\n {\n Write-Output \" Converting single object to object array...\"\n $json.exportScopes = @($json.exportScopes)\n }\n }\n\n Write-Output \" Renaming to 'scopes'...\"\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\n $json.PSObject.Properties.Remove('exportScopes')\n }\n\n # Force string array to object array with unique values\n if ($json.scopes)\n {\n Write-Output \" Converting string array to object array...\"\n $scopeArray = @()\n $json.scopes | Where-Object $validateScopes | ForEach-Object { $scopeArray += $_ } | Select-Object -Unique\n $json.scopes = @() + $scopeArray\n }\n}\n\n# Set default if not found\nif (!$json)\n{\n Write-Output \"No existing settings.json file found. Creating new file...\"\n $json = [ordered]@{\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\n type = 'HubInstance'\n version = ''\n learnMore = 'https://aka.ms/finops/hubs'\n scopes = @()\n retention = @{\n 'msexports' = @{\n days = 0\n }\n 'ingestion' = @{\n months = 13\n }\n 'raw' = @{\n days = 0\n }\n 'final' = @{\n months = 13\n }\n }\n }\n\n $text = $json | ConvertTo-Json\n Write-Output \"---------\"\n Write-Output $text\n Write-Output \"---------\"\n}\n\n# Set default retention\nif (!($json.retention))\n{\n # In case the retention object is not present in the settings.json file (versions before 0.4), add it with default values\n $retention = @\"\n {\n \"msexports\": {\n \"days\": 0\n },\n \"ingestion\": {\n \"months\": 13\n },\n \"raw\": {\n \"days\": 0\n },\n \"final\": {\n \"months\": 13\n }\n }\n\"@\n $json | Add-Member -Name retention -Value (ConvertFrom-Json $retention) -MemberType NoteProperty\n}\n\n# Set or update msexports retention\nif (!($json.retention.msexports))\n{\n $json.retention | Add-Member -Name msexports -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:msexportRetentionInDays)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.msexports.days = [Int32]::Parse($env:msexportRetentionInDays)\n}\n\n# Set or update ingestion retention\nif (!($json.retention.ingestion))\n{\n $json.retention | Add-Member -Name ingestion -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:ingestionRetentionInMonths)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.ingestion.months = [Int32]::Parse($env:ingestionRetentionInMonths)\n}\n\n# Set or update raw retention\nif (!($json.retention.raw))\n{\n $json.retention | Add-Member -Name raw -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:rawRetentionInDays)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.raw.days = [Int32]::Parse($env:rawRetentionInDays)\n}\n\n# Set or update final retention\nif (!($json.retention.final))\n{\n $json.retention | Add-Member -Name final -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:finalRetentionInMonths)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.final.months = [Int32]::Parse($env:finalRetentionInMonths)\n}\n\n# Updating settings\nWrite-Output \"Updating version to $env:ftkVersion...\"\n$json.version = $env:ftkVersion\n$json.scopes = (@() + $json.scopes + $newScopes) | Select-Object -Unique\nif ($null -eq $json.scopes) { $json.scopes = @() }\n$text = $json | ConvertTo-Json\nWrite-Output \"---------\"\nWrite-Output $text\nWrite-Output \"---------\"\n$text | Out-File $filePath\n\n# Upload new/updated settings\nWrite-Output \"Uploading settings.json file...\"\nSet-AzStorageBlobContent @storageContext -File $filePath -Force | Out-Null\n", + "CONFIG": "config", + "INGESTION": "ingestion", + "finOpsToolkitVersion": "12.0" + }, + "resources": { + "dataFactory::dataset_config": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]", "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" + "linkedServiceName": { + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + }, + "type": "Json", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().fileName}", + "type": "Expression" + }, + "folderPath": { + "value": "@{dataset().folderPath}", + "type": "Expression" + } + } + }, + "parameters": { + "fileName": { + "type": "String", + "defaultValue": "settings.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[reference('configContainer').outputs.containerName.value]" + } } }, "dependsOn": [ - "queuePrivateDnsZone" + "appRegistration", + "configContainer" ] }, - "tablePrivateDnsZone::tablePrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.table.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.table.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "dataFactory::dataset_ingestion": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('INGESTION'))]", "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" + }, + "fileSystem": "[reference('ingestionContainer').outputs.containerName.value]" + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" } }, "dependsOn": [ - "tablePrivateDnsZone" + "appRegistration", + "ingestionContainer" ] }, - "scriptEndpoint::scriptPrivateDnsZoneGroup": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', string(parameters('hub').routing.scriptStorage)), 'blob-endpoint-zone')]", + "dataFactory::dataset_ingestion_files": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', variables('INGESTION')))]", "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', string(parameters('hub').routing.dnsZones.blob.name))]" - } + "annotations": [], + "parameters": { + "folderPath": { + "type": "String" } - ] - }, - "dependsOn": [ - "blobPrivateDnsZone", - "scriptEndpoint" - ] - }, - "nsg": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2023-11-01", - "name": "[variables('nsgName')]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/networkSecurityGroups')]", - "properties": { - "securityRules": [ - { - "name": "AllowVnetInBound", - "properties": { - "priority": 100, - "direction": "Inbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "VirtualNetwork", - "destinationAddressPrefix": "VirtualNetwork" - } - }, - { - "name": "AllowAzureLoadBalancerInBound", - "properties": { - "priority": 200, - "direction": "Inbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "AzureLoadBalancer", - "destinationAddressPrefix": "*" - } - }, - { - "name": "DenyAllInBound", - "properties": { - "priority": 4096, - "direction": "Inbound", - "access": "Deny", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*" - } - }, - { - "name": "AllowVnetOutBound", - "properties": { - "priority": 100, - "direction": "Outbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "VirtualNetwork", - "destinationAddressPrefix": "VirtualNetwork" - } - }, - { - "name": "AllowInternetOutBound", - "properties": { - "priority": 200, - "direction": "Outbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "Internet" - } - }, - { - "name": "DenyAllOutBound", - "properties": { - "priority": 4096, - "direction": "Outbound", - "access": "Deny", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*" + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileSystem": "[reference('ingestionContainer').outputs.containerName.value]", + "folderPath": { + "value": "@dataset().folderPath", + "type": "Expression" } } - ] - } - }, - "vNet": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2023-11-01", - "name": "[parameters('hub').routing.networkName]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/virtualNetworks')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[parameters('hub').options.networkAddressPrefix]" - ] }, - "subnets": "[variables('subnets')]" + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + } }, "dependsOn": [ - "nsg" - ] - }, - "blobPrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.dfs.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "queuePrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.queue.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "tablePrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.table.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" + "appRegistration", + "ingestionContainer" ] }, - "scriptStorageAccount": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[string(parameters('hub').routing.scriptStorage)]", - "location": "[parameters('hub').location]", - "sku": { - "name": "Standard_LRS" - }, - "kind": "StorageV2", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/storageAccounts')]", - "properties": { - "supportsHttpsTrafficOnly": true, - "allowSharedKeyAccess": true, - "isHnsEnabled": false, - "minimumTlsVersion": "TLS1_2", - "allowBlobPublicAccess": false, - "publicNetworkAccess": "Enabled", - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Deny", - "virtualNetworkRules": [ - { - "id": "[parameters('hub').routing.subnets.scripts]", - "action": "Allow" - } - ] - } - }, + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", "dependsOn": [ - "vNet::scriptSubnet" + "appRegistration" ] }, - "scriptEndpoint": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', string(parameters('hub').routing.scriptStorage))]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateEndpoints')]", + "infrastructure": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_Infrastructure", "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" + "expressionEvaluationOptions": { + "scope": "inner" }, - "privateLinkServiceConnections": [ - { - "name": "scriptLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', string(parameters('hub').routing.scriptStorage))]", - "groupIds": [ - "blob" - ] - } + "mode": "Incremental", + "parameters": { + "hub": { + "value": "[parameters('app').hub]" } - ] - }, - "dependsOn": [ - "scriptStorageAccount", - "vNet::scriptSubnet" - ] - } - }, - "outputs": { - "config": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "FinOps hub configuration settings." - }, - "value": "[parameters('hub')]" - }, - "vNetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the virtual network." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks', parameters('hub').routing.networkName))]" - }, - "vNetAddressSpace": { - "type": "array", - "metadata": { - "description": "Virtual network address prefixes." - }, - "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').addressSpace.addressPrefixes)]" - }, - "vNetSubnets": { - "type": "array", - "metadata": { - "description": "Virtual network subnets." - }, - "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').subnets)]" - }, - "finopsHubSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the FinOps hub network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('finopsHubSubnetName')))]" - }, - "scriptSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the script storage account network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('scriptSubnetName')))]" - }, - "dataExplorerSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Explorer network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('dataExplorerSubnetName')))]" - } - } - } - } - }, - "core": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[variables('hub')]" - }, - "telemetryString": { - "value": "[variables('telemetryString')]" - }, - "scopesToMonitor": { - "value": "[parameters('scopesToMonitor')]" - }, - "msexportRetentionInDays": { - "value": "[parameters('exportRetentionInDays')]" - }, - "ingestionRetentionInMonths": { - "value": "[parameters('ingestionRetentionInMonths')]" - }, - "rawRetentionInDays": { - "value": "[parameters('dataExplorerRawRetentionInDays')]" - }, - "finalRetentionInMonths": { - "value": "[parameters('dataExplorerFinalRetentionInMonths')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "15396896523851766061" - } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "5983517136492624194" } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" + }, + "definitions": { + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } }, - "scripts": { - "type": "string" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance to deploy the app to." - } - }, - "scopesToMonitor": { - "type": "array", - "metadata": { - "description": "Optional. List of scope IDs to monitor and ingest cost for." - } - }, - "msexportRetentionInDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Optional. Number of days of data to retain in the msexports container. Default: 0." - } - }, - "ingestionRetentionInMonths": { - "type": "int", - "defaultValue": 13, - "metadata": { - "description": "Optional. Number of months of data to retain in the ingestion container. Default: 13." - } - }, - "rawRetentionInDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." - } - }, - "finalRetentionInMonths": { - "type": "int", - "defaultValue": 13, - "metadata": { - "description": "Optional. Number of months of data to retain in the Data Explorer *_final_v* tables. Default: 13." - } - }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - } - }, - "variables": { - "$fxv#0": "12.0", - "$fxv#1": "12.0", - "$fxv#2": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nWrite-Output \"Updating settings.json file...\"\r\nWrite-Output \" Storage account: $env:storageAccountName\"\r\nWrite-Output \" Container: $env:containerName\"\r\n\r\n$validateScopes = { $_.Length -gt 45 }\r\n\r\n# Initialize variables\r\n$fileName = 'settings.json'\r\n$filePath = Join-Path -Path . -ChildPath $fileName\r\n$newScopes = $env:scopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Download existing settings, if they exist\r\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\r\nif ($blob)\r\n{\r\n $text = Get-Content $filePath -Raw\r\n Write-Output \"---------\"\r\n Write-Output $text\r\n Write-Output \"---------\"\r\n $json = $text | ConvertFrom-Json\r\n Write-Output \"Existing settings.json file found. Updating...\"\r\n\r\n # Rename exportScopes to scopes + convert to object array\r\n if ($json.exportScopes)\r\n {\r\n Write-Output \" Updating exportScopes...\"\r\n if ($json.exportScopes[0] -is [string])\r\n {\r\n Write-Output \" Converting string array to object array...\"\r\n $json.exportScopes = @($json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } })\r\n if (-not ($json.exportScopes -is [array]))\r\n {\r\n Write-Output \" Converting single object to object array...\"\r\n $json.exportScopes = @($json.exportScopes)\r\n }\r\n }\r\n\r\n Write-Output \" Renaming to 'scopes'...\"\r\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\r\n $json.PSObject.Properties.Remove('exportScopes')\r\n }\r\n\r\n # Force string array to object array with unique values\r\n if ($json.scopes)\r\n {\r\n Write-Output \" Converting string array to object array...\"\r\n $scopeArray = @()\r\n $json.scopes | Where-Object $validateScopes | ForEach-Object { $scopeArray += $_ } | Select-Object -Unique\r\n $json.scopes = @() + $scopeArray\r\n }\r\n}\r\n\r\n# Set default if not found\r\nif (!$json)\r\n{\r\n Write-Output \"No existing settings.json file found. Creating new file...\"\r\n $json = [ordered]@{\r\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\r\n type = 'HubInstance'\r\n version = ''\r\n learnMore = 'https://aka.ms/finops/hubs'\r\n scopes = @()\r\n retention = @{\r\n 'msexports' = @{\r\n days = 0\r\n }\r\n 'ingestion' = @{\r\n months = 13\r\n }\r\n 'raw' = @{\r\n days = 0\r\n }\r\n 'final' = @{\r\n months = 13\r\n }\r\n }\r\n }\r\n\r\n $text = $json | ConvertTo-Json\r\n Write-Output \"---------\"\r\n Write-Output $text\r\n Write-Output \"---------\"\r\n}\r\n\r\n# Set default retention\r\nif (!($json.retention))\r\n{\r\n # In case the retention object is not present in the settings.json file (versions before 0.4), add it with default values\r\n $retention = @\"\r\n {\r\n \"msexports\": {\r\n \"days\": 0\r\n },\r\n \"ingestion\": {\r\n \"months\": 13\r\n },\r\n \"raw\": {\r\n \"days\": 0\r\n },\r\n \"final\": {\r\n \"months\": 13\r\n }\r\n }\r\n\"@\r\n $json | Add-Member -Name retention -Value (ConvertFrom-Json $retention) -MemberType NoteProperty\r\n}\r\n\r\n# Set or update msexports retention\r\nif (!($json.retention.msexports))\r\n{\r\n $json.retention | Add-Member -Name msexports -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:msexportRetentionInDays)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.msexports.days = [Int32]::Parse($env:msexportRetentionInDays)\r\n}\r\n\r\n# Set or update ingestion retention\r\nif (!($json.retention.ingestion))\r\n{\r\n $json.retention | Add-Member -Name ingestion -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:ingestionRetentionInMonths)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.ingestion.months = [Int32]::Parse($env:ingestionRetentionInMonths)\r\n}\r\n\r\n# Set or update raw retention\r\nif (!($json.retention.raw))\r\n{\r\n $json.retention | Add-Member -Name raw -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:rawRetentionInDays)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.raw.days = [Int32]::Parse($env:rawRetentionInDays)\r\n}\r\n\r\n# Set or update final retention\r\nif (!($json.retention.final))\r\n{\r\n $json.retention | Add-Member -Name final -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:finalRetentionInMonths)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.final.months = [Int32]::Parse($env:finalRetentionInMonths)\r\n}\r\n\r\n# Updating settings\r\nWrite-Output \"Updating version to $env:ftkVersion...\"\r\n$json.version = $env:ftkVersion\r\n$json.scopes = (@() + $json.scopes + $newScopes) | Select-Object -Unique\r\nif ($null -eq $json.scopes) { $json.scopes = @() }\r\n$text = $json | ConvertTo-Json\r\nWrite-Output \"---------\"\r\nWrite-Output $text\r\nWrite-Output \"---------\"\r\n$text | Out-File $filePath\r\n\r\n# Upload new/updated settings\r\nWrite-Output \"Uploading settings.json file...\"\r\nSet-AzStorageBlobContent @storageContext -File $filePath -Force | Out-Null\r\n" - }, - "resources": { - "appRegistration": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_Register", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[parameters('hub')]" - }, - "publisher": { - "value": "Microsoft FinOps hubs" - }, - "namespace": { - "value": "Microsoft.FinOpsHubs" - }, - "appName": { - "value": "Core" - }, - "displayName": { - "value": "FinOps hub core" - }, - "appVersion": { - "value": "[variables('$fxv#0')]" - }, - "features": { - "value": [ - "DataFactory", - "Storage" - ] - }, - "telemetryString": { - "value": "[parameters('telemetryString')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "15179190433979236138" - } - }, - "definitions": { - "_1.HubRoutingProperties": { + "HubProperties": { "type": "object", "properties": { - "networkId": { + "id": { "type": "string" }, - "networkName": { + "name": { "type": "string" }, - "scriptStorage": { + "location": { "type": "string" }, - "dnsZones": { + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { "type": "object", "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "enableTelemetry": { + "type": "bool" }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "keyVaultSku": { + "type": "string" }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "networkAddressPrefix": { + "type": "string" }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } }, - "subnets": { + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { "type": "object", "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { + "suffix": { "type": "string" } } } }, "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppFeature": { - "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], - "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." }, "routing": "FinOps hub private network routing properties, if enabled.", "core": { @@ -2377,7 +1762,7 @@ "apps": {}, "description": "FinOps hub instance properties.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "../../fx/hub-types.bicep" } } } @@ -2386,56 +1771,7 @@ { "namespace": "__bicep", "members": { - "getAppTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - }, - { - "type": "bool", - "nullable": true, - "name": "forceAppTags" - } - ], - "output": { - "type": "object", - "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "getPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "newApp": { + "getHubTags": { "parameters": [ { "$ref": "#/definitions/HubProperties", @@ -2443,511 +1779,434 @@ }, { "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "appPartialName" - }, - { - "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" + "name": "resourceType" } ], "output": { - "$ref": "#/definitions/HubAppProperties", - "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" + "type": "object", + "value": "[union(parameters('hub').tags, coalesce(tryGet(parameters('hub').tagsByResource, parameters('resourceType')), createObject()))]" }, "metadata": { - "description": "Creates a new FinOps hub app configuration object.", + "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "../../fx/hub-types.bicep" } } } } + } + ], + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "Required. FinOps hub instance properties." + } + } + }, + "variables": { + "nsgName": "[format('{0}-nsg', parameters('hub').routing.networkName)]", + "finopsHubSubnetName": "private-endpoint-subnet", + "scriptSubnetName": "script-subnet", + "dataExplorerSubnetName": "dataExplorer-subnet", + "subnets": "[if(not(parameters('hub').options.privateRouting), createArray(), createArray(createObject('name', variables('finopsHubSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 0), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('scriptSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'delegations', createArray(createObject('name', 'Microsoft.ContainerInstance/containerGroups', 'properties', createObject('serviceName', 'Microsoft.ContainerInstance/containerGroups'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('dataExplorerSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 27, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName')))))))]" + }, + "resources": { + "vNet::finopsHubSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('finopsHubSubnetName'))]", + "dependsOn": [ + "vNet" + ] }, - { - "namespace": "_1", - "members": { - "newAppInternal": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherSuffix" - }, - { - "type": "object", - "name": "publisherTags" - }, - { - "type": "string", - "name": "appName" - }, - { - "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" - } - ], - "output": { - "$ref": "#/definitions/HubAppProperties", - "value": { - "name": "[parameters('appName')]", - "displayName": "[parameters('appDisplayName')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", - "publisher": { - "name": "[parameters('publisherName')]", - "displayName": "[parameters('publisherDisplayName')]", - "suffix": "[parameters('publisherSuffix')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" - }, - "hub": "[parameters('hub')]", - "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "safeStorageName": { - "parameters": [ - { - "type": "string", - "name": "name" - } - ], - "output": { - "type": "string", - "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance properties." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app publisher." - } - }, - "namespace": { - "type": "string", - "metadata": { - "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - }, - "appName": { - "type": "string", - "metadata": { - "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - }, - "displayName": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app." - } - }, - "appVersion": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Version number of the FinOps hub app." - } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." - } - }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - } - }, - "variables": { - "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", - "version": "[parameters('appVersion')]" - } - }, - "resources": [] - } - }, - "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" - }, - "resources": { - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', variables('app').storage, 'default')]", + "vNet::scriptSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('scriptSubnetName'))]", "dependsOn": [ - "storageAccount" + "vNet" ] }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "vNet::dataExplorerSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('dataExplorerSubnetName'))]", + "dependsOn": [ + "vNet" + ] + }, + "blobPrivateDnsZone::blobPrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.blob.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.blob.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + } }, "dependsOn": [ - "blobEndpoint" + "blobPrivateDnsZone" ] }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", + "dfsPrivateDnsZone::dfsPrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.dfs.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.dfs.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + } }, "dependsOn": [ - "dfsEndpoint" + "dfsPrivateDnsZone" ] }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", + "queuePrivateDnsZone::queuePrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.queue.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.queue.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + } }, "dependsOn": [ - "dataFactory", - "keyVault" + "queuePrivateDnsZone" ] }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "tablePrivateDnsZone::tablePrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.table.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.table.name), '.', '-')))]", "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { + "registrationEnabled": false, "virtualNetwork": { "id": "[parameters('hub').routing.networkId]" - }, - "registrationEnabled": false + } }, "dependsOn": [ - "keyVaultPrivateDnsZone" + "tablePrivateDnsZone" ] }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "scriptEndpoint::scriptPrivateDnsZoneGroup": { + "condition": "[parameters('hub').options.privateRouting]", "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('hub').routing.scriptStorage), 'blob-endpoint-zone')]", "properties": { "privateDnsZoneConfigs": [ { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', string(parameters('hub').routing.dnsZones.blob.name))]" } } ] }, "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" + "blobPrivateDnsZone", + "scriptEndpoint" ] }, - "appTelemetry": { - "condition": "[parameters('hub').options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", - "properties": "[variables('telemetryProps')]" - }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[variables('app').dataFactory]", - "location": "[variables('app').hub.location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" - } - } - }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[variables('app').storage]", - "location": "[parameters('hub').location]", - "sku": { - "name": "[parameters('hub').options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" - }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", + "nsg": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/networkSecurityGroups", "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', variables('app').storage)]", + "name": "[variables('nsgName')]", "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/networkSecurityGroups')]", "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ + "securityRules": [ { - "name": "blobLink", + "name": "AllowVnetInBound", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "blob" - ] + "priority": 100, + "direction": "Inbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "VirtualNetwork", + "destinationAddressPrefix": "VirtualNetwork" + } + }, + { + "name": "AllowAzureLoadBalancerInBound", + "properties": { + "priority": 200, + "direction": "Inbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "AzureLoadBalancer", + "destinationAddressPrefix": "*" + } + }, + { + "name": "DenyAllInBound", + "properties": { + "priority": 4096, + "direction": "Inbound", + "access": "Deny", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "AllowVnetOutBound", + "properties": { + "priority": 100, + "direction": "Outbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "VirtualNetwork", + "destinationAddressPrefix": "VirtualNetwork" + } + }, + { + "name": "AllowInternetOutBound", + "properties": { + "priority": 200, + "direction": "Outbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "Internet" + } + }, + { + "name": "DenyAllOutBound", + "properties": { + "priority": 4096, + "direction": "Outbound", + "access": "Deny", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "*" } } ] + } + }, + "vNet": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2023-11-01", + "name": "[parameters('hub').routing.networkName]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/virtualNetworks')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[parameters('hub').options.networkAddressPrefix]" + ] + }, + "subnets": "[variables('subnets')]" }, "dependsOn": [ - "storageAccount" + "nsg" ] }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, + "blobPrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", "type": "Microsoft.Network/privateDnsZones", "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', variables('app').storage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] - }, + "dfsPrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.dfs.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, "dependsOn": [ - "storageAccount" + "vNet" ] }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[variables('app').keyVault]", + "queuePrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.queue.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] + }, + "tablePrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.table.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] + }, + "scriptStorageAccount": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('hub').routing.scriptStorage]", "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/storageAccounts')]", "properties": { - "sku": { - "name": "[parameters('hub').options.keyVaultSku]", - "family": "A" - }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ], + "supportsHttpsTrafficOnly": true, + "allowSharedKeyAccess": true, + "isHnsEnabled": false, + "minimumTlsVersion": "TLS1_2", + "allowBlobPublicAccess": false, + "publicNetworkAccess": "Enabled", "networkAcls": { "bypass": "AzureServices", - "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "id": "[parameters('hub').routing.subnets.scripts]", + "action": "Allow" + } + ] } }, "dependsOn": [ - "dataFactory" + "vNet::scriptSubnet" ] }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} - }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "scriptEndpoint": { + "condition": "[parameters('hub').options.privateRouting]", "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', variables('app').keyVault)]", + "name": "[format('{0}-blob-ep', parameters('hub').routing.scriptStorage)]", "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateEndpoints')]", "properties": { "subnet": { - "id": "[parameters('hub').routing.subnets.keyVault]" + "id": "[parameters('hub').routing.subnets.storage]" }, "privateLinkServiceConnections": [ { - "name": "keyVaultLink", + "name": "scriptLink", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('hub').routing.scriptStorage)]", "groupIds": [ - "vault" + "blob" ] } } ] }, "dependsOn": [ - "keyVault" + "scriptStorageAccount", + "vNet::scriptSubnet" ] } }, "outputs": { - "app": { - "$ref": "#/definitions/HubAppProperties", + "config": { + "$ref": "#/definitions/HubProperties", "metadata": { - "description": "FinOps hub app configuration." + "description": "FinOps hub configuration settings." }, - "value": "[variables('app')]" + "value": "[parameters('hub')]" }, - "principalId": { + "vNetId": { "type": "string", "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." + "description": "Resource ID of the virtual network." }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks', parameters('hub').routing.networkName))]" + }, + "vNetAddressSpace": { + "type": "array", + "metadata": { + "description": "Virtual network address prefixes." + }, + "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').addressSpace.addressPrefixes)]" + }, + "vNetSubnets": { + "type": "array", + "metadata": { + "description": "Virtual network subnets." + }, + "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').subnets)]" + }, + "finopsHubSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the FinOps hub network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('finopsHubSubnetName')))]" + }, + "scriptSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the script storage account network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('scriptSubnetName')))]" + }, + "dataExplorerSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Data Explorer network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('dataExplorerSubnetName')))]" } } } } }, - "configContainer": { + "appRegistration": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_Storage.ConfigContainer", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_Register", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -2955,13 +2214,16 @@ "mode": "Incremental", "parameters": { "app": { - "value": "[reference('appRegistration').outputs.app.value]" + "value": "[parameters('app')]" }, - "container": { - "value": "config" + "version": { + "value": "[variables('finOpsToolkitVersion')]" }, - "forceCreateBlobManagerIdentity": { - "value": true + "features": { + "value": [ + "DataFactory", + "Storage" + ] } }, "template": { @@ -2971,8 +2233,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "13960345490822271084" + "version": "0.39.26.7824", + "templateHash": "5436870138046688593" } }, "definitions": { @@ -3094,6 +2356,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -3120,6 +2385,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -3150,38 +2416,38 @@ } } }, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -3190,22 +2456,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -3213,194 +2478,1015 @@ } } }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." + "description": "Required. FinOps hub app getting deployed." } }, - "container": { + "version": { "type": "string", "metadata": { - "description": "Required. Name of the storage container to create or update." + "description": "Required. Version number of the FinOps hub app." } }, - "files": { - "type": "object", - "defaultValue": {}, + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." + "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." } }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, + "storageRoles": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." + "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + } + }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." } } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0}', parameters('app').id)]", + "version": "[parameters('version')]" + } + }, + "resources": [] + } + }, + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", + "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" }, "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", "properties": { - "publicAccess": "None", - "metadata": {} - } + "name": "[parameters('app').storage]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "storageAccount" + ] + }, + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", + "properties": { + "name": "[parameters('app').keyVault]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "keyVault" + ] + }, + "dataFactory::managedVirtualNetwork": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "properties": {}, + "dependsOn": [ + "dataFactory" + ] + }, + "dataFactory::managedIntegrationRuntime": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "default", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('app').hub.location]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedVirtualNetwork" + ] + }, + "dataFactory::linkedService_keyVault": { + "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "keyVault" + ] + }, + "dataFactory::linkedService_storageAccount": { + "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "storageAccount" + ] }, "storageAccount::blobService": { - "existing": true, + "condition": "[variables('usesStorage')]", "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "dfsEndpoint" + ] + }, + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('app').hub.options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } + } + }, + "storageRoleAssignments": { + "copy": { + "name": "storageRoleAssignments", + "count": "[length(variables('factoryStorageRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "storageAccount" + ] + }, + "triggerManagerIdentity": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "dependsOn": [ + "dataFactory" + ] + }, + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "triggerManagerIdentity" + ] }, "storageAccount": { - "existing": true, + "condition": "[variables('usesStorage')]", "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" + "name": "[parameters('app').storage]", + "location": "[parameters('app').hub.location]", + "sku": { + "name": "[parameters('app').hub.options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Identity', deployment().name)]", + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + }, + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "roles": { - "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" - ] - } + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "4534337491931150093" + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "blob" + ] } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('app').hub.options.keyVaultSku]", + "family": "A" + }, + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + } + }, + "dependsOn": [ + "dataFactory" + ] + }, + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('app').keyVault)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.keyVault]" + }, + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } + ] + }, + "dependsOn": [ + "keyVault" + ] + }, + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" } - }, + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", + "getStoragePrivateEndpointConnections", + "keyVault" + ] + }, + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "_1.HubRoutingProperties": { - "type": "object", + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections", + "keyVault" + ] + }, + "getStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", + "stopTriggers", + "storageAccount" + ] + }, + "approveStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getStoragePrivateEndpointConnections", + "storageAccount" + ] + }, + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('app').dataFactory]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" }, "scriptStorage": { "type": "string" @@ -3425,6 +3511,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -3451,6 +3540,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -3484,34 +3574,20 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { "type": "string" }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } + "suffix": { + "type": "string" }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "tags": { + "type": "object" }, "dataFactory": { "type": "string" @@ -3521,22 +3597,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -3544,629 +3619,229 @@ } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the identity is associated with." + "description": "Required. FinOps hub app the deployment script is being run for." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the user assigned identity." + "description": "Required. Name of the managed identity to create." } }, - "roleAssignmentResourceId": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." } }, - "roles": { + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/EnvironmentVariable" }, + "defaultValue": [], "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." + "description": "Optional. Environment variables to use for the deployment script." } } }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, "resources": { "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", "location": "[parameters('app').hub.location]" }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" + "count": "[length(variables('privateEndpointDeploymentRoles'))]" }, + "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } }, - "value": "[reference('identity').principalId]" + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } - } + }, + "dependsOn": [ + "appTelemetry", + "dataFactory", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" + ] + } + }, + "outputs": { + "dataFactoryId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Data Factory instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Upload', deployment().name)]", + "keyVaultId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Key Vault instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + }, + "triggerManagerIdentityName": { + "type": "string", + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + } + } + } + }, + "dependsOn": [ + "infrastructure" + ] + }, + "configContainer": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_Storage.ConfigContainer", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "container": { + "value": "[variables('CONFIG')]" + }, + "forceCreateBlobManagerIdentity": { + "value": true + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "7314877606184110283" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - } + "name": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "keyVaultSku": { + "type": "string" }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "networkAddressPrefix": { + "type": "string" }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "containerName": { - "type": "string", - "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" - }, - "filesUploaded": { - "type": "int", - "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" - }, - "identityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" - }, - "identityPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" - } - } - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "ingestionContainer": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_Storage.IngestionContainer", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[reference('appRegistration').outputs.app.value]" - }, - "container": { - "value": "ingestion" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "13960345490822271084" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" + "privateRouting": { + "type": "bool" }, "publisherIsolation": { "type": "bool" @@ -4250,6 +3925,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -4276,6 +3954,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -4309,35 +3988,21 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -4346,22 +4011,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -4398,7 +4062,7 @@ } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", "fileCount": "[length(items(parameters('files')))]", "hasFiles": "[greater(variables('fileCount'), 0)]" }, @@ -4427,7 +4091,7 @@ "identity": { "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}.Identity', deployment().name)]", "properties": { "expressionEvaluationOptions": { @@ -4458,8 +4122,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "4534337491931150093" + "version": "0.39.26.7824", + "templateHash": "2980528181281411934" } }, "definitions": { @@ -4581,6 +4245,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -4607,6 +4274,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -4640,35 +4308,21 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -4677,22 +4331,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -4704,7 +4357,7 @@ { "namespace": "__bicep", "members": { - "getPublisherTags": { + "getAppPublisherTags": { "parameters": [ { "$ref": "#/definitions/HubAppProperties", @@ -4717,7 +4370,7 @@ ], "output": { "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" }, "metadata": { "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", @@ -4763,7 +4416,7 @@ "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", "location": "[parameters('app').hub.location]" }, "identityRoleAssignments": { @@ -4813,7 +4466,7 @@ "uploadFiles": { "condition": "[variables('hasFiles')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}.Upload', deployment().name)]", "properties": { "expressionEvaluationOptions": { @@ -4854,8 +4507,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" } }, "definitions": { @@ -4988,6 +4641,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -5014,6 +4670,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -5047,35 +4704,21 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -5084,22 +4727,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -5153,7 +4795,8 @@ }, "variables": { "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" }, "resources": { "identity": { @@ -5202,7 +4845,7 @@ "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} } }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", "dependsOn": [ "identity", "identityRoleAssignments" @@ -5259,10 +4902,10 @@ "appRegistration" ] }, - "uploadSettings": { + "ingestionContainer": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_Storage.UpdateSettings", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_Storage.IngestionContainer", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -5270,52 +4913,10 @@ "mode": "Incremental", "parameters": { "app": { - "value": "[reference('appRegistration').outputs.app.value]" - }, - "identityName": { - "value": "[reference('configContainer').outputs.identityName.value]" - }, - "scriptName": { - "value": "[format('{0}_uploadSettings', reference('appRegistration').outputs.app.value.storage)]" - }, - "environmentVariables": { - "value": [ - { - "name": "ftkVersion", - "value": "[variables('$fxv#1')]" - }, - { - "name": "scopes", - "value": "[join(parameters('scopesToMonitor'), '|')]" - }, - { - "name": "msexportRetentionInDays", - "value": "[string(parameters('msexportRetentionInDays'))]" - }, - { - "name": "ingestionRetentionInMonths", - "value": "[string(parameters('ingestionRetentionInMonths'))]" - }, - { - "name": "rawRetentionInDays", - "value": "[string(parameters('rawRetentionInDays'))]" - }, - { - "name": "finalRetentionInMonths", - "value": "[string(parameters('finalRetentionInMonths'))]" - }, - { - "name": "storageAccountName", - "value": "[reference('appRegistration').outputs.app.value.storage]" - }, - { - "name": "containerName", - "value": "config" - } - ] + "value": "[parameters('app')]" }, - "scriptContent": { - "value": "[variables('$fxv#2')]" + "container": { + "value": "[variables('INGESTION')]" } }, "template": { @@ -5325,22 +4926,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" + "version": "0.39.26.7824", + "templateHash": "7314877606184110283" } }, "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "_1.HubProperties": { "type": "object", "properties": { @@ -5459,6 +5049,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -5485,6 +5078,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -5518,35 +5112,21 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -5555,22 +5135,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -5582,2756 +5161,437 @@ "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + "description": "Required. FinOps hub app that storage is getting updated for." } }, - "scriptContent": { + "container": { "type": "string", "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Required. Name of the storage container to create or update." } }, - "arguments": { - "type": "string", - "defaultValue": "", + "files": { + "type": "object", + "defaultValue": {}, "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." } }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." } } }, "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" }, "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "properties": { + "publicAccess": "None", + "metadata": {} + } }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", + "storageAccount::blobService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + }, + "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "name": "[parameters('app').storage]" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Identity', deployment().name)]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "appRegistration", - "configContainer" - ] - } - }, - "outputs": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Name of the Data Factory." - }, - "value": "[reference('appRegistration').outputs.app.value.dataFactory]" - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." - }, - "value": "[reference('appRegistration').outputs.app.value.storage]" - }, - "configContainer": { - "type": "string", - "metadata": { - "description": "The name of the container used for configuration settings." - }, - "value": "[reference('configContainer').outputs.containerName.value]" - }, - "ingestionContainer": { - "type": "string", - "metadata": { - "description": "The name of the container used for normalized data ingestion." - }, - "value": "[reference('ingestionContainer').outputs.containerName.value]" - }, - "storageUrlForPowerBI": { - "type": "string", - "metadata": { - "description": "URL to use when connecting custom Power BI reports to your data." - }, - "value": "[format('https://{0}.dfs.{1}/{2}', reference('appRegistration').outputs.app.value.storage, environment().suffixes.storage, reference('ingestionContainer').outputs.containerName.value)]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Object ID of the Data Factory managed identity. This will be needed when configuring managed exports." - }, - "value": "[reference('appRegistration').outputs.principalId.value]" - }, - "publisherTags": { - "type": "object", - "metadata": { - "description": "Tags for the FinOps hub publisher." - }, - "value": "[reference('appRegistration').outputs.app.value.publisher.tags]" - } - } - } - }, - "dependsOn": [ - "infrastructure" - ] - }, - "cmExports": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.CostManagement.Exports", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[variables('hub')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "12652260421176486151" - } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance to deploy the app to." - } - } - }, - "variables": { - "$fxv#0": "12.0", - "$fxv#1": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"Date\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectivePrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Cost\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceLocation\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedService\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo1\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo2\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AdditionalInfo\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSection\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceGroup\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PlanName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeType\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Frequency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherType\"}\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#10": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Kind\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Kind\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservedHours\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ReservedHours\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TotalReservedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"TotalReservedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"UsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsedHours\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UsedHours\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#11": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"SKU\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SKU\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Location\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CostWithNoReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostWithNoReservedInstances\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"FirstUsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"FirstUsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"LookBackPeriod\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"NetSavings\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"NetSavings\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"NormalizedSize\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NormalizedSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RecommendedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RecommendedQuantityNormalized\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Scope\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Scope\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuProperties\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuProperties\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TotalCostWithReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TotalCostWithReservedInstances\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#12": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"Cost With No ReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostWithNoReservedInstancesJson\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"First UsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"FirstUsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Instance Flexibility Ratio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Instance Flexibility Group\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Location\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"LookBackPeriod\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Net Savings\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NetSavingsJson\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Normalized Size\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NormalizedSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Recommended Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Recommended Quantity Normalized\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"scope\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Scope\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Sku Properties\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuProperties\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Total Cost With ReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TotalCostWithReservedInstancesJson\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#13": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"AccountOwnerEmail\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerEmail\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Amount\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ArmSkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingMonth\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingMonth\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CurrentEnrollmentId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CurrentEnrollmentId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"DepartmentName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"DepartmentName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Description\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EventDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EventType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MonetaryCommitment\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MonetaryCommitment\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Overage\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Overage\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingEnrollment\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingEnrollment\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#14": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Amount\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ArmSkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Description\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EventDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EventType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Invoice\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Invoice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#2": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"Date\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectivePrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Cost\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceLocation\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedService\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo1\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo2\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AdditionalInfo\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSection\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceGroup\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PlanName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeType\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Frequency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherType\"}\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#3": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuMeter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AmortizationClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ServiceModel\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPlanName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#4": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuMeter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AmortizationClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ServiceModel\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPlanName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#5": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#6": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#7": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UsageQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UsageUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ChargeId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ChargeId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#8": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"EnrollmentNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EnrollmentNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProductID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveStartDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveEndDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BasePrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MarketPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CurrencyCode\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CurrencyCode\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"IncludedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"IncludedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"OfferID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PriceType\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#9": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProductId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TierMinimumUnits\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TierMinimumUnits\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveStartDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveEndDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BasePrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MarketPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PriceType\" }\r\n }\r\n ]\r\n }\r\n}\r\n" - }, - "resources": { - "appRegistration": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.CostManagement.Exports_Register", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[parameters('hub')]" - }, - "publisher": { - "value": "Microsoft FinOps hubs" - }, - "namespace": { - "value": "Microsoft.FinOpsHubs" - }, - "appName": { - "value": "Core" - }, - "displayName": { - "value": "FinOps hub core" - }, - "appVersion": { - "value": "[variables('$fxv#0')]" - }, - "features": { - "value": [ - "DataFactory", - "Storage" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "15179190433979236138" - } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppFeature": { - "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], - "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - }, - { - "type": "bool", - "nullable": true, - "name": "forceAppTags" - } - ], - "output": { - "type": "object", - "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "getPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "newApp": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "appPartialName" - }, - { - "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" - } - ], - "output": { - "$ref": "#/definitions/HubAppProperties", - "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" - }, - "metadata": { - "description": "Creates a new FinOps hub app configuration object.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - }, - { - "namespace": "_1", - "members": { - "newAppInternal": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherSuffix" - }, - { - "type": "object", - "name": "publisherTags" - }, - { - "type": "string", - "name": "appName" - }, - { - "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" - } - ], - "output": { - "$ref": "#/definitions/HubAppProperties", - "value": { - "name": "[parameters('appName')]", - "displayName": "[parameters('appDisplayName')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", - "publisher": { - "name": "[parameters('publisherName')]", - "displayName": "[parameters('publisherDisplayName')]", - "suffix": "[parameters('publisherSuffix')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" - }, - "hub": "[parameters('hub')]", - "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "safeStorageName": { - "parameters": [ - { - "type": "string", - "name": "name" - } - ], - "output": { - "type": "string", - "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance properties." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app publisher." - } - }, - "namespace": { - "type": "string", - "metadata": { - "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - }, - "appName": { - "type": "string", - "metadata": { - "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - }, - "displayName": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app." - } - }, - "appVersion": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Version number of the FinOps hub app." - } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." - } - }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - } - }, - "variables": { - "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", - "version": "[parameters('appVersion')]" - } - }, - "resources": [] - } - }, - "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" - }, - "resources": { - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', variables('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] - }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] - }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] - }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] - }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] - }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" - } - } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] - }, - "appTelemetry": { - "condition": "[parameters('hub').options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", - "properties": "[variables('telemetryProps')]" - }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[variables('app').dataFactory]", - "location": "[variables('app').hub.location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" - } - } - }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[variables('app').storage]", - "location": "[parameters('hub').location]", - "sku": { - "name": "[parameters('hub').options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" - }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', variables('app').storage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "blob" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" - }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', variables('app').storage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[variables('app').keyVault]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", - "properties": { - "sku": { - "name": "[parameters('hub').options.keyVaultSku]", - "family": "A" - }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" - } - }, - "dependsOn": [ - "dataFactory" - ] - }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} - }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', variables('app').keyVault)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.keyVault]" - }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] - }, - "dependsOn": [ - "keyVault" - ] - } - }, - "outputs": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "FinOps hub app configuration." - }, - "value": "[variables('app')]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - } - } - } - } - }, - "schemaFiles": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.CostManagement.Exports_Storage.SchemaFiles", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[reference('appRegistration').outputs.app.value]" - }, - "container": { - "value": "config" - }, - "files": { - "value": { - "schemas/actualcost_c360-2025-04.json": "[variables('$fxv#1')]", - "schemas/amortizedcost_c360-2025-04.json": "[variables('$fxv#2')]", - "schemas/focuscost_1.2.json": "[variables('$fxv#3')]", - "schemas/focuscost_1.2-preview.json": "[variables('$fxv#4')]", - "schemas/focuscost_1.0r2.json": "[variables('$fxv#5')]", - "schemas/focuscost_1.0.json": "[variables('$fxv#6')]", - "schemas/focuscost_1.0-preview(v1).json": "[variables('$fxv#7')]", - "schemas/pricesheet_2023-05-01_ea.json": "[variables('$fxv#8')]", - "schemas/pricesheet_2023-05-01_mca.json": "[variables('$fxv#9')]", - "schemas/reservationdetails_2023-03-01.json": "[variables('$fxv#10')]", - "schemas/reservationrecommendations_2023-05-01_ea.json": "[variables('$fxv#11')]", - "schemas/reservationrecommendations_2023-05-01_mca.json": "[variables('$fxv#12')]", - "schemas/reservationtransactions_2023-05-01_ea.json": "[variables('$fxv#13')]", - "schemas/reservationtransactions_2023-05-01_mca.json": "[variables('$fxv#14')]" - } - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "13960345490822271084" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." - } - }, - "container": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage container to create or update." - } - }, - "files": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." - } - }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" - }, - "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", - "properties": { - "publicAccess": "None", - "metadata": {} - } - }, - "storageAccount::blobService": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" - }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Identity', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "roles": { - "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "4534337491931150093" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the identity is associated with." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the user assigned identity." - } - }, - "roleAssignmentResourceId": { - "type": "string", - "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." - } - }, - "roles": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." - } - } - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", - "location": "[parameters('app').hub.location]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." - }, - "value": "[reference('identity').principalId]" - } - } - } - } - }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Upload', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + "identityName": { + "value": "[format('{0}_blobManager', parameters('app').storage)]" }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "containerName": { - "type": "string", - "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" - }, - "filesUploaded": { - "type": "int", - "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" - }, - "identityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" - }, - "identityPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" - } - } - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "exportContainer": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.CostManagement.Exports_Storage.ExportContainer", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[reference('appRegistration').outputs.app.value]" - }, - "container": { - "value": "msexports" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "13960345490822271084" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "roles": { + "value": [ + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" + ] + } }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2980528181281411934" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the identity is associated with." + } }, - "keyVault": { - "type": "string" + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the user assigned identity." + } }, - "scripts": { - "type": "string" + "roleAssignmentResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource access is being granted for." + } }, - "storage": { - "type": "string" + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of RBAC role assignment GUIDs." + } } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "location": "[parameters('app').hub.location]" }, - "displayName": { - "type": "string" + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" }, - "suffix": { - "type": "string" + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." + }, + "value": "[parameters('identityName')]" }, - "tags": { - "type": "object" + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" } } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" } } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." - } - }, - "container": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage container to create or update." - } - }, - "files": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." - } - }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" - }, - "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", - "properties": { - "publicAccess": "None", - "metadata": {} - } - }, - "storageAccount::blobService": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "uploadFiles": { + "condition": "[variables('hasFiles')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Identity', deployment().name)]", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Upload', deployment().name)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -8342,16 +5602,26 @@ "value": "[parameters('app')]" }, "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + "value": "[reference('identity').outputs.name.value]" }, - "roles": { + "environmentVariables": { "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" + }, + { + "name": "files", + "value": "[string(parameters('files'))]" + } ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" } }, "template": { @@ -8361,11 +5631,22 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "4534337491931150093" + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" } }, "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "_1.HubProperties": { "type": "object", "properties": { @@ -8484,6 +5765,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -8510,6 +5794,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -8543,35 +5828,21 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -8580,22 +5851,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -8603,3437 +5873,7014 @@ } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the identity is associated with." + "description": "Required. FinOps hub app the deployment script is being run for." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the user assigned identity." + "description": "Required. Name of the managed identity to create." } }, - "roleAssignmentResourceId": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." } }, - "roles": { + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/EnvironmentVariable" }, + "defaultValue": [], "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." + "description": "Optional. Environment variables to use for the deployment script." } } }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, "resources": { "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", "location": "[parameters('app').hub.location]" }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" + "count": "[length(variables('privateEndpointDeploymentRoles'))]" }, + "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } }, - "value": "[reference('identity').principalId]" + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" + }, + "filesUploaded": { + "type": "int", + "metadata": { + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" + }, + "identityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + } + } + } + }, + "dependsOn": [ + "appRegistration" + ] + }, + "uploadSettings": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_Storage.UpdateSettings", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[reference('configContainer').outputs.identityName.value]" + }, + "scriptName": { + "value": "[format('{0}_uploadSettings', parameters('app').storage)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "environmentVariables": { + "value": [ + { + "name": "ftkVersion", + "value": "[variables('finOpsToolkitVersion')]" + }, + { + "name": "scopes", + "value": "[join(parameters('scopesToMonitor'), '|')]" + }, + { + "name": "msexportRetentionInDays", + "value": "[string(parameters('msexportRetentionInDays'))]" + }, + { + "name": "ingestionRetentionInMonths", + "value": "[string(parameters('ingestionRetentionInMonths'))]" + }, + { + "name": "rawRetentionInDays", + "value": "[string(parameters('rawRetentionInDays'))]" + }, + { + "name": "finalRetentionInMonths", + "value": "[string(parameters('finalRetentionInMonths'))]" + }, + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "config" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } } }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Upload', deployment().name)]", + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - } + "name": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "keyVaultSku": { + "type": "string" }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } + "networkAddressPrefix": { + "type": "string" }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } + "privateRouting": { + "type": "bool" }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } + "publisherIsolation": { + "type": "bool" }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } + "storageInfrastructureEncryption": { + "type": "bool" }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "dataFactory": { + "type": "string" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] + "keyVault": { + "type": "string" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" } } } }, - "dependsOn": [ - "identity" - ] + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } }, - "outputs": { - "containerName": { - "type": "string", + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - "filesUploaded": { - "type": "int", + "identityName": { + "type": "string", "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" + "description": "Required. Name of the managed identity to create." + } }, - "identityId": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } }, - "identityName": { + "scriptContent": { "type": "string", "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + "description": "Required. Name of the deployment script to create." + } }, - "identityPrincipalId": { + "arguments": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } }, "dependsOn": [ - "appRegistration" + "appRegistration", + "configContainer" ] } }, "outputs": { - "exportContainer": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Properties of the hub app." + }, + "value": "[parameters('app')]" + }, + "dataFactoryName": { "type": "string", "metadata": { - "description": "Name of the container used for Cost Management exports." + "description": "Name of the Data Factory." }, - "value": "[reference('exportContainer').outputs.containerName.value]" + "value": "[parameters('app').dataFactory]" }, - "schemaFilesUploaded": { - "type": "int", + "storageAccountName": { + "type": "string", "metadata": { - "description": "Number of schema files uploaded." + "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." }, - "value": "[reference('schemaFiles').outputs.filesUploaded.value]" + "value": "[parameters('app').storage]" + }, + "storageUrlForPowerBI": { + "type": "string", + "metadata": { + "description": "URL to use when connecting custom Power BI reports to your data." + }, + "value": "[format('https://{0}.dfs.{1}/{2}', parameters('app').storage, environment().suffixes.storage, variables('INGESTION'))]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Object ID of the Data Factory managed identity. This will be needed when configuring managed exports." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + }, + "triggerManagerIdentityName": { + "type": "string", + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[reference('appRegistration').outputs.triggerManagerIdentityName.value]" } } } - }, - "dependsOn": [ - "core" - ] + } }, - "dataExplorer": { - "condition": "[variables('deployDataExplorer')]", + "cmExports": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "dataExplorer", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.Exports", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[parameters('dataExplorerName')]" - }, - "clusterSku": { - "value": "[parameters('dataExplorerSku')]" - }, - "clusterCapacity": { - "value": "[parameters('dataExplorerCapacity')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "tags": { - "value": "[variables('hub').tags]" - }, - "tagsByResource": { - "value": "[parameters('tagsByResource')]" - }, - "dataFactoryName": { - "value": "[reference('core').outputs.dataFactoryName.value]" - }, - "rawRetentionInDays": { - "value": "[parameters('dataExplorerRawRetentionInDays')]" - }, - "virtualNetworkId": "[if(parameters('enablePublicAccess'), createObject('value', ''), createObject('value', reference('infrastructure').outputs.vNetId.value))]", - "privateEndpointSubnetId": "[if(parameters('enablePublicAccess'), createObject('value', ''), createObject('value', reference('infrastructure').outputs.dataExplorerSubnetId.value))]", - "enablePublicAccess": { - "value": "[parameters('enablePublicAccess')]" - }, - "storageAccountName": { - "value": "[reference('core').outputs.storageAccountName.value]" + "mode": "Incremental", + "parameters": { + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft.CostManagement', 'Exports')]" } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "12711851392414163333" + "version": "0.39.26.7824", + "templateHash": "12146592525418089958" } }, - "parameters": { - "clusterName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics. If empty, Azure Data Explorer will not be deployed. Required to use with Power BI if you have more than $2-5M/mo in costs being monitored. Default: \"\" (do not use)." - } - }, - "clusterSku": { - "type": "string", - "defaultValue": "Dev(No SLA)_Standard_E2a_v4", - "allowedValues": [ - "Dev(No SLA)_Standard_E2a_v4", - "Dev(No SLA)_Standard_D11_v2", - "Standard_D11_v2", - "Standard_D12_v2", - "Standard_D13_v2", - "Standard_D14_v2", - "Standard_D16d_v5", - "Standard_D32d_v4", - "Standard_D32d_v5", - "Standard_DS13_v2+1TB_PS", - "Standard_DS13_v2+2TB_PS", - "Standard_DS14_v2+3TB_PS", - "Standard_DS14_v2+4TB_PS", - "Standard_E2a_v4", - "Standard_E2ads_v5", - "Standard_E2d_v4", - "Standard_E2d_v5", - "Standard_E4a_v4", - "Standard_E4ads_v5", - "Standard_E4d_v4", - "Standard_E4d_v5", - "Standard_E8a_v4", - "Standard_E8ads_v5", - "Standard_E8as_v4+1TB_PS", - "Standard_E8as_v4+2TB_PS", - "Standard_E8as_v5+1TB_PS", - "Standard_E8as_v5+2TB_PS", - "Standard_E8d_v4", - "Standard_E8d_v5", - "Standard_E8s_v4+1TB_PS", - "Standard_E8s_v4+2TB_PS", - "Standard_E8s_v5+1TB_PS", - "Standard_E8s_v5+2TB_PS", - "Standard_E16a_v4", - "Standard_E16ads_v5", - "Standard_E16as_v4+3TB_PS", - "Standard_E16as_v4+4TB_PS", - "Standard_E16as_v5+3TB_PS", - "Standard_E16as_v5+4TB_PS", - "Standard_E16d_v4", - "Standard_E16d_v5", - "Standard_E16s_v4+3TB_PS", - "Standard_E16s_v4+4TB_PS", - "Standard_E16s_v5+3TB_PS", - "Standard_E16s_v5+4TB_PS", - "Standard_E64i_v3", - "Standard_E80ids_v4", - "Standard_EC8ads_v5", - "Standard_EC8as_v5+1TB_PS", - "Standard_EC8as_v5+2TB_PS", - "Standard_EC16ads_v5", - "Standard_EC16as_v5+3TB_PS", - "Standard_EC16as_v5+4TB_PS", - "Standard_L4s", - "Standard_L8as_v3", - "Standard_L8s", - "Standard_L8s_v2", - "Standard_L8s_v3", - "Standard_L16as_v3", - "Standard_L16s", - "Standard_L16s_v2", - "Standard_L16s_v3", - "Standard_L32as_v3", - "Standard_L32s_v3" - ], - "metadata": { - "description": "Optional. Name of the Azure Data Explorer SKU. Default: \"Dev(No SLA)_Standard_E2a_v4\"." - } - }, - "clusterCapacity": { - "type": "int", - "defaultValue": 1, - "minValue": 1, - "maxValue": 1000, - "metadata": { - "description": "Optional. Number of nodes to use in the cluster. Allowed values: 1 for the Basic SKU tier and 2-1000 for Standard. Default: 1 for dev/test SKUs, 2 for standard SKUs." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } } }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Optional. Azure location to use for the managed identity and deployment script to auto-start triggers. Default: (resource group location)." + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } } }, - "tags": { + "_1.IdNameObject": { "type": "object", - "defaultValue": {}, + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, "metadata": { - "description": "Optional. Tags to apply to all resources." + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } } }, - "tagsByResource": { + "HubAppProperties": { "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." - } - }, - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory instance." - } - }, - "rawRetentionInDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account to use for data ingestion." - } - }, - "virtualNetworkId": { - "type": "string", - "metadata": { - "description": "Required. Resource ID of the virtual network for private endpoints." - } - }, - "privateEndpointSubnetId": { - "type": "string", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, "metadata": { - "description": "Required. Resource ID of the subnet for private endpoints." + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } } - }, - "enablePublicAccess": { - "type": "bool", + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Optional. Enable public access." + "description": "Required. FinOps hub app getting deployed." } } }, "variables": { - "$fxv#0": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_1(id: string) {\r\n dynamic({\r\n \"arizeai.observabilityeval/organizations\": { \"SingularDisplayName\": \"Azure Native Arize AI Cloud Service\" }\r\n ,\"astronomer.astro/organizations\": { \"SingularDisplayName\": \"Astro Organization\" }\r\n ,\"citrix.services/xenappessentials\": { \"SingularDisplayName\": \"Citrix Virtual Apps Essentials\" }\r\n ,\"citrix.services/xendesktopessentials\": { \"SingularDisplayName\": \"Citrix Virtual Desktops Essentials\" }\r\n ,\"commvault.contentstore/cloudaccounts\": { \"SingularDisplayName\": \"Commvault Cloud Account\" }\r\n ,\"commvault.contentstore/cloudaccounts/plans\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts plan\" }\r\n ,\"commvault.contentstore/cloudaccounts/protectiongroups\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection group\" }\r\n ,\"commvault.contentstore/cloudaccounts/protectiongroups/protecteditems\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection groups protected item\" }\r\n ,\"commvault.contentstore/cloudaccounts/storages\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts storage\" }\r\n ,\"dell.storage/filesystems\": { \"SingularDisplayName\": \"Dell PowerScale\" }\r\n ,\"dynatrace.observability/monitors\": { \"SingularDisplayName\": \"Dynatrace\" }\r\n ,\"github.network/networksettings\": { \"SingularDisplayName\": \"GitHub.Network network setting\" }\r\n ,\"informatica.datamanagement/organizations\": { \"SingularDisplayName\": \"Informatica Organization\" }\r\n ,\"lambdatest.hyperexecute/organizations\": { \"SingularDisplayName\": \"Azure Native LambdaTest - HyperExecute Cloud Service\" }\r\n ,\"microsoft.aad/domainservices\": { \"SingularDisplayName\": \"Microsoft Entra Domain Services\" }\r\n ,\"microsoft.aadiam/diagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.aadiam diagnostic setting\" }\r\n ,\"microsoft.aadiam/privatelinkforazuread\": { \"SingularDisplayName\": \"Private Link for Microsoft Entra ID\" }\r\n ,\"microsoft.advisor/advisorscore\": { \"SingularDisplayName\": \"Microsoft.Advisor advisor score\" }\r\n ,\"microsoft.advisor/assessments\": { \"SingularDisplayName\": \"Microsoft.Advisor assessment\" }\r\n ,\"microsoft.advisor/configurations\": { \"SingularDisplayName\": \"Microsoft.Advisor configuration\" }\r\n ,\"microsoft.advisor/generaterecommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor generate recommendation\" }\r\n ,\"microsoft.advisor/metadata\": { \"SingularDisplayName\": \"Microsoft.Advisor metadata\" }\r\n ,\"microsoft.advisor/recommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendation\" }\r\n ,\"microsoft.advisor/recommendations/suppressions\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendations suppression\" }\r\n ,\"microsoft.advisor/resiliencyreviews\": { \"SingularDisplayName\": \"Microsoft.Advisor resiliency review\" }\r\n ,\"microsoft.agfoodplatform/farmbeats\": { \"SingularDisplayName\": \"Azure Data Manager for Agriculture\" }\r\n ,\"microsoft.agfoodplatform/farmbeatsextensiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats extension definition\" }\r\n ,\"microsoft.agfoodplatform/farmbeatssolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats solution definition\" }\r\n ,\"microsoft.agricultureplatform/agriservices\": { \"SingularDisplayName\": \"Agriculture data solutions\" }\r\n ,\"microsoft.akshybrid/agentpools\": { \"SingularDisplayName\": \"Microsoft.AksHybrid agent pool\" }\r\n ,\"microsoft.akshybrid/provisionedclusters\": { \"SingularDisplayName\": \"Microsoft.AksHybrid provisioned cluster\" }\r\n ,\"microsoft.akshybrid/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.AksHybrid upgrade profile\" }\r\n ,\"microsoft.alertsmanagement/actionrules\": { \"SingularDisplayName\": \"Alert processing rule\" }\r\n ,\"microsoft.alertsmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alert\" }\r\n ,\"microsoft.alertsmanagement/alerts/enrichments\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alerts enrichment\" }\r\n ,\"microsoft.alertsmanagement/prometheusrulegroups\": { \"SingularDisplayName\": \"Prometheus rule group\" }\r\n ,\"microsoft.alertsmanagement/smartdetectoralertrules\": { \"SingularDisplayName\": \"Smart detector alert rule\" }\r\n ,\"microsoft.alertsmanagement/smartgroups\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement smart group\" }\r\n ,\"microsoft.alertsmanagement/tenantactivitylogalerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement tenant activity log alert\" }\r\n ,\"microsoft.all/arcvirtualmachines\": { \"SingularDisplayName\": \"Azure Arc virtual machine\" }\r\n ,\"microsoft.all/hcivirtualmachines\": { \"SingularDisplayName\": \"Azure Local Virtual Machine - Azure Arc\" }\r\n ,\"microsoft.all/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.analysisservices/servers\": { \"SingularDisplayName\": \"Analysis Services server\" }\r\n ,\"microsoft.anybuild/clusters\": { \"SingularDisplayName\": \"AnyBuild cluster\" }\r\n ,\"microsoft.apicenter/deletedservices\": { \"SingularDisplayName\": \"Microsoft.ApiCenter deleted service\" }\r\n ,\"microsoft.apicenter/services\": { \"SingularDisplayName\": \"API Center\" }\r\n ,\"microsoft.apicenter/services/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.apimanagement/gateways\": { \"SingularDisplayName\": \"API Management gateway\" }\r\n ,\"microsoft.apimanagement/gateways/configconnections\": { \"SingularDisplayName\": \"Microsoft.ApiManagement gateways config connection\" }\r\n ,\"microsoft.apimanagement/service\": { \"SingularDisplayName\": \"API Management service\" }\r\n ,\"microsoft.apimanagement/service/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.apisecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.ApiSecurity defender setting\" }\r\n ,\"microsoft.app/agents\": { \"SingularDisplayName\": \"SRE Agent\" }\r\n ,\"microsoft.app/builders\": { \"SingularDisplayName\": \"Microsoft.App builder\" }\r\n ,\"microsoft.app/builders/builds\": { \"SingularDisplayName\": \"Microsoft.App builders build\" }\r\n ,\"microsoft.app/connectedenvironments\": { \"SingularDisplayName\": \"Container Apps Connected Environment\" }\r\n ,\"microsoft.app/containerapps\": { \"SingularDisplayName\": \"Container App\" }\r\n ,\"microsoft.app/jobs\": { \"SingularDisplayName\": \"Container App Job\" }\r\n ,\"microsoft.app/logicapps\": { \"SingularDisplayName\": \"Logic app\" }\r\n ,\"microsoft.app/logicapps/workflows\": { \"SingularDisplayName\": \"Logic app workflow\" }\r\n ,\"microsoft.app/managedenvironments\": { \"SingularDisplayName\": \"Container Apps Environment\" }\r\n ,\"microsoft.app/sessionpools\": { \"SingularDisplayName\": \"Container App Session Pool\" }\r\n ,\"microsoft.app/spaces\": { \"SingularDisplayName\": \"App Space\" }\r\n ,\"microsoft.appassessment/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate project\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessment\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedapplications\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed application\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed machine\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/machinestoassess\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments machines to asses\" }\r\n ,\"microsoft.appassessment/migrateprojects/sites\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects site\" }\r\n ,\"microsoft.appassessment/migrateprojects/sites/applianceconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects sites appliance configuration\" }\r\n ,\"microsoft.appcomplianceautomation/reports\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation report\" }\r\n ,\"microsoft.appcomplianceautomation/reports/evidences\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports evidence\" }\r\n ,\"microsoft.appcomplianceautomation/reports/scopingconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports scoping configuration\" }\r\n ,\"microsoft.appcomplianceautomation/reports/snapshots\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshot\" }\r\n ,\"microsoft.appcomplianceautomation/reports/snapshots/controls\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshots control\" }\r\n ,\"microsoft.appcomplianceautomation/reports/webhooks\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports webhook\" }\r\n ,\"microsoft.appconfiguration/configurationstores\": { \"SingularDisplayName\": \"App Configuration\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hub\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs/applications\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs application\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs/applications/members\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs applications member\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsite\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites agent\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqldatabases\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqldatabase\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqlinstances\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqlinstance\" }\r\n ,\"microsoft.appplatform/spring\": { \"SingularDisplayName\": \"Azure Spring Apps\" }\r\n ,\"microsoft.appsecurity/appprotectmanagedrulesetmanifests\": { \"SingularDisplayName\": \"Microsoft.AppSecurity app protect managed rule set manifest\" }\r\n ,\"microsoft.appsecurity/policies\": { \"SingularDisplayName\": \"App Protect Policy\" }\r\n ,\"microsoft.arc/all\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\r\n ,\"microsoft.arc/allfairfax\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\r\n ,\"microsoft.arc/kubernetesresources\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\r\n ,\"microsoft.arc/kubernetesresourcesfairfax\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\r\n ,\"microsoft.arcnetworking/arcnwloadbalancers\": { \"SingularDisplayName\": \"Microsoft.ArcNetworking arc nw load balancer\" }\r\n ,\"microsoft.aszlabhardware/labservers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware labserver\" }\r\n ,\"microsoft.aszlabhardware/reservations\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservation\" }\r\n ,\"microsoft.aszlabhardware/reservations/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservations server\" }\r\n ,\"microsoft.aszlabhardware/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware server\" }\r\n ,\"microsoft.attestation/attestationproviders\": { \"SingularDisplayName\": \"Attestation provider\" }\r\n ,\"microsoft.authorization/accessreviewhistorydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review history definition\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definition\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instance\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances/decisions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instances decision\" }\r\n ,\"microsoft.authorization/accessreviewschedulesettings\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule setting\" }\r\n ,\"microsoft.authorization/datapolicymanifests\": { \"SingularDisplayName\": \"Microsoft.Authorization data policy manifest\" }\r\n ,\"microsoft.authorization/denyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization deny assignment\" }\r\n ,\"microsoft.authorization/locks\": { \"SingularDisplayName\": \"Microsoft.Authorization lock\" }\r\n ,\"microsoft.authorization/policyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization policy assignment\" }\r\n ,\"microsoft.authorization/policydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definition\" }\r\n ,\"microsoft.authorization/policydefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definitions version\" }\r\n ,\"microsoft.authorization/policyexemptions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy exemption\" }\r\n ,\"microsoft.authorization/policysetdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definition\" }\r\n ,\"microsoft.authorization/policysetdefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definitions version\" }\r\n ,\"microsoft.authorization/privatelinkassociations\": { \"SingularDisplayName\": \"Microsoft.Authorization private link association\" }\r\n ,\"microsoft.authorization/provideroperations\": { \"SingularDisplayName\": \"Microsoft.Authorization provider operation\" }\r\n ,\"microsoft.authorization/resourcemanagementprivatelinks\": { \"SingularDisplayName\": \"Resource management private link\" }\r\n ,\"microsoft.authorization/roleassignmentapprovals\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approval\" }\r\n ,\"microsoft.authorization/roleassignmentapprovals/stages\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approvals stage\" }\r\n ,\"microsoft.authorization/roleassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment\" }\r\n ,\"microsoft.authorization/roleassignmentscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule instance\" }\r\n ,\"microsoft.authorization/roleassignmentschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule request\" }\r\n ,\"microsoft.authorization/roleassignmentschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule\" }\r\n ,\"microsoft.authorization/roledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role definition\" }\r\n ,\"microsoft.authorization/roleeligibilityscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule instance\" }\r\n ,\"microsoft.authorization/roleeligibilityschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule request\" }\r\n ,\"microsoft.authorization/roleeligibilityschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule\" }\r\n ,\"microsoft.authorization/rolemanagementalertconfigurations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert configuration\" }\r\n ,\"microsoft.authorization/rolemanagementalertdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert definition\" }\r\n ,\"microsoft.authorization/rolemanagementalertoperations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert operation\" }\r\n ,\"microsoft.authorization/rolemanagementalerts\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert\" }\r\n ,\"microsoft.authorization/rolemanagementalerts/alertincidents\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alerts alert incident\" }\r\n ,\"microsoft.authorization/rolemanagementpolicies\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy\" }\r\n ,\"microsoft.authorization/rolemanagementpolicyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy assignment\" }\r\n ,\"microsoft.automanage/bestpractices\": { \"SingularDisplayName\": \"Microsoft.Automanage best practice\" }\r\n ,\"microsoft.automanage/bestpractices/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage best practices version\" }\r\n ,\"microsoft.automanage/configurationprofileassignments\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignment\" }\r\n ,\"microsoft.automanage/configurationprofileassignments/reports\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignments report\" }\r\n ,\"microsoft.automanage/configurationprofiles\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile\" }\r\n ,\"microsoft.automanage/configurationprofiles/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profiles version\" }\r\n ,\"microsoft.automanage/serviceprincipals\": { \"SingularDisplayName\": \"ServicePrincipals\" }\r\n ,\"microsoft.automation/automationaccounts\": { \"SingularDisplayName\": \"Automation account\" }\r\n ,\"microsoft.automation/automationaccounts/hybridrunbookworkergroups\": { \"SingularDisplayName\": \"Automation hybrid worker group\" }\r\n ,\"microsoft.automation/automationaccounts/runbooks\": { \"SingularDisplayName\": \"Automation runbook\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform account\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/accounts/datapools\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform accounts data pool\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform workspace\" }\r\n ,\"microsoft.avs/privateclouds\": { \"SingularDisplayName\": \"Azure VMware Solution private cloud\" }\r\n ,\"microsoft.awsconnector/accessanalyzeranalyzers\": { \"SingularDisplayName\": \"Access Analyzer Analyzer\" }\r\n ,\"microsoft.awsconnector/acmcertificatesummaries\": { \"SingularDisplayName\": \"ACM Certificate Summary\" }\r\n ,\"microsoft.awsconnector/apigatewayrestapis\": { \"SingularDisplayName\": \"Api Gateway Rest Api\" }\r\n ,\"microsoft.awsconnector/apigatewaystages\": { \"SingularDisplayName\": \"Api Gateway Stage\" }\r\n ,\"microsoft.awsconnector/applicationautoscalingscalabletargets\": { \"SingularDisplayName\": \"Application Auto Scaling Scalable Target\" }\r\n ,\"microsoft.awsconnector/appsyncgraphqlapis\": { \"SingularDisplayName\": \"App Sync Graphql Api\" }\r\n ,\"microsoft.awsconnector/autoscalingautoscalinggroups\": { \"SingularDisplayName\": \"Auto Scaling Auto Scaling Group\" }\r\n ,\"microsoft.awsconnector/cloudformationstacks\": { \"SingularDisplayName\": \"Cloud Formation Stack\" }\r\n ,\"microsoft.awsconnector/cloudformationstacksets\": { \"SingularDisplayName\": \"Cloud Formation Stack Set\" }\r\n ,\"microsoft.awsconnector/cloudfrontdistributions\": { \"SingularDisplayName\": \"Cloud Front Distribution\" }\r\n ,\"microsoft.awsconnector/cloudtrailtrails\": { \"SingularDisplayName\": \"Cloud Trail Trail\" }\r\n ,\"microsoft.awsconnector/cloudwatchalarms\": { \"SingularDisplayName\": \"Cloud Watch Alarm\" }\r\n ,\"microsoft.awsconnector/codebuildprojects\": { \"SingularDisplayName\": \"Code Build Project\" }\r\n ,\"microsoft.awsconnector/codebuildsourcecredentialsinfos\": { \"SingularDisplayName\": \"Code Build Source Credentials Info\" }\r\n ,\"microsoft.awsconnector/configserviceconfigurationrecorders\": { \"SingularDisplayName\": \"Config Service Configuration Recorder\" }\r\n ,\"microsoft.awsconnector/configserviceconfigurationrecorderstatuses\": { \"SingularDisplayName\": \"Config Service Configuration Recorder Status\" }\r\n ,\"microsoft.awsconnector/configservicedeliverychannels\": { \"SingularDisplayName\": \"Config Service Delivery Channel\" }\r\n ,\"microsoft.awsconnector/databasemigrationservicereplicationinstances\": { \"SingularDisplayName\": \"Database Migration Service Replication Instance\" }\r\n ,\"microsoft.awsconnector/daxclusters\": { \"SingularDisplayName\": \"DAX Cluster\" }\r\n ,\"microsoft.awsconnector/dynamodbcontinuousbackupsdescriptions\": { \"SingularDisplayName\": \"Dynamo DB Continuous Backups Description\" }\r\n ,\"microsoft.awsconnector/dynamodbtables\": { \"SingularDisplayName\": \"Dynamo DB Table\" }\r\n ,\"microsoft.awsconnector/ec2accountattributes\": { \"SingularDisplayName\": \"EC2 Account Attribute\" }\r\n ,\"microsoft.awsconnector/ec2addresses\": { \"SingularDisplayName\": \"EC2 Address\" }\r\n ,\"microsoft.awsconnector/ec2flowlogs\": { \"SingularDisplayName\": \"EC2 Flow Log\" }\r\n ,\"microsoft.awsconnector/ec2images\": { \"SingularDisplayName\": \"EC2 Image\" }\r\n ,\"microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\r\n ,\"microsoft.awsconnector/ec2instancestatuses\": { \"SingularDisplayName\": \"EC2 Instance Status\" }\r\n ,\"microsoft.awsconnector/ec2ipams\": { \"SingularDisplayName\": \"EC2 Ipam\" }\r\n ,\"microsoft.awsconnector/ec2keypairs\": { \"SingularDisplayName\": \"EC2 Key Pair\" }\r\n ,\"microsoft.awsconnector/ec2networkacls\": { \"SingularDisplayName\": \"EC2 Network Acl\" }\r\n ,\"microsoft.awsconnector/ec2networkinterfaces\": { \"SingularDisplayName\": \"EC2 Network Interface\" }\r\n ,\"microsoft.awsconnector/ec2routetables\": { \"SingularDisplayName\": \"EC2 Route Table\" }\r\n ,\"microsoft.awsconnector/ec2securitygroups\": { \"SingularDisplayName\": \"EC2 Security Group\" }\r\n ,\"microsoft.awsconnector/ec2snapshots\": { \"SingularDisplayName\": \"EC2 Snapshot\" }\r\n ,\"microsoft.awsconnector/ec2subnets\": { \"SingularDisplayName\": \"EC2 Subnet\" }\r\n ,\"microsoft.awsconnector/ec2volumes\": { \"SingularDisplayName\": \"EC2 Volume\" }\r\n ,\"microsoft.awsconnector/ec2vpcendpoints\": { \"SingularDisplayName\": \"EC2 VPCEndpoint\" }\r\n ,\"microsoft.awsconnector/ec2vpcpeeringconnections\": { \"SingularDisplayName\": \"EC2 VPCPeering Connection\" }\r\n ,\"microsoft.awsconnector/ec2vpcs\": { \"SingularDisplayName\": \"EC2 VPC\" }\r\n ,\"microsoft.awsconnector/ecrimagedetails\": { \"SingularDisplayName\": \"ECR Image Detail\" }\r\n ,\"microsoft.awsconnector/ecrrepositories\": { \"SingularDisplayName\": \"ECR Repository\" }\r\n ,\"microsoft.awsconnector/ecsclusters\": { \"SingularDisplayName\": \"ECS Cluster\" }\r\n ,\"microsoft.awsconnector/ecsservices\": { \"SingularDisplayName\": \"ECS Service\" }\r\n ,\"microsoft.awsconnector/ecstaskdefinitions\": { \"SingularDisplayName\": \"ECS Task Definition\" }\r\n ,\"microsoft.awsconnector/efsfilesystems\": { \"SingularDisplayName\": \"EFS File System\" }\r\n ,\"microsoft.awsconnector/efsmounttargets\": { \"SingularDisplayName\": \"EFS Mount Target\" }\r\n ,\"microsoft.awsconnector/eksnodegroups\": { \"SingularDisplayName\": \"EKS Nodegroup\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkapplications\": { \"SingularDisplayName\": \"Elastic Beanstalk Application\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkconfigurationtemplates\": { \"SingularDisplayName\": \"Elastic Beanstalk Configuration Template\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkenvironments\": { \"SingularDisplayName\": \"Elastic Beanstalk Environment\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2listeners\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Listener\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2loadbalancers\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Load Balancer\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2targetgroups\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Target Group\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2targethealthdescriptions\": { \"SingularDisplayName\": \"Elastic Load Balancing v2 Target Health Description\" }\r\n ,\"microsoft.awsconnector/elasticsearchdomains\": { \"SingularDisplayName\": \"Elasticsearch Domain\" }\r\n ,\"microsoft.awsconnector/emrclusters\": { \"SingularDisplayName\": \"EMR Cluster\" }\r\n ,\"microsoft.awsconnector/guarddutydetectors\": { \"SingularDisplayName\": \"Guard Duty Detector\" }\r\n ,\"microsoft.awsconnector/iamaccesskeylastuseds\": { \"SingularDisplayName\": \"IAM Access Key Last Used\" }\r\n ,\"microsoft.awsconnector/iamaccesskeymetadata\": { \"SingularDisplayName\": \"IAM Access Key Metadata\" }\r\n ,\"microsoft.awsconnector/iamgroups\": { \"SingularDisplayName\": \"IAM Group\" }\r\n ,\"microsoft.awsconnector/iaminstanceprofiles\": { \"SingularDisplayName\": \"IAM Instance Profile\" }\r\n ,\"microsoft.awsconnector/iammanagedpolicies\": { \"SingularDisplayName\": \"IAM Managed Policy\" }\r\n ,\"microsoft.awsconnector/iammfadevices\": { \"SingularDisplayName\": \"IAM MFADevice\" }\r\n ,\"microsoft.awsconnector/iampasswordpolicies\": { \"SingularDisplayName\": \"IAM Password Policy\" }\r\n ,\"microsoft.awsconnector/iampolicyversions\": { \"SingularDisplayName\": \"IAM Policy Version\" }\r\n ,\"microsoft.awsconnector/iamroles\": { \"SingularDisplayName\": \"IAM Role\" }\r\n ,\"microsoft.awsconnector/iamservercertificates\": { \"SingularDisplayName\": \"IAM Server Certificate\" }\r\n ,\"microsoft.awsconnector/iamuserpolicies\": { \"SingularDisplayName\": \"IAM User Policy\" }\r\n ,\"microsoft.awsconnector/iamvirtualmfadevices\": { \"SingularDisplayName\": \"IAM Virtual MFADevice\" }\r\n ,\"microsoft.awsconnector/kmsaliases\": { \"SingularDisplayName\": \"KMS Alias\" }\r\n ,\"microsoft.awsconnector/kmskeys\": { \"SingularDisplayName\": \"KMS Key\" }\r\n ,\"microsoft.awsconnector/lambdafunctioncodelocations\": { \"SingularDisplayName\": \"Lambda Function Code Location\" }\r\n ,\"microsoft.awsconnector/lambdafunctionconfigurations\": { \"SingularDisplayName\": \"Microsoft.AwsConnector lambda function configuration\" }\r\n ,\"microsoft.awsconnector/lambdafunctions\": { \"SingularDisplayName\": \"Lambda Function\" }\r\n ,\"microsoft.awsconnector/licensemanagerlicenses\": { \"SingularDisplayName\": \"License Manager License\" }\r\n ,\"microsoft.awsconnector/lightsailbuckets\": { \"SingularDisplayName\": \"Lightsail Bucket\" }\r\n ,\"microsoft.awsconnector/lightsailinstances\": { \"SingularDisplayName\": \"Lightsail Instance\" }\r\n ,\"microsoft.awsconnector/logsloggroups\": { \"SingularDisplayName\": \"Logs Log Group\" }\r\n ,\"microsoft.awsconnector/logslogstreams\": { \"SingularDisplayName\": \"Logs Log Stream\" }\r\n ,\"microsoft.awsconnector/logsmetricfilters\": { \"SingularDisplayName\": \"Logs Metric Filter\" }\r\n ,\"microsoft.awsconnector/logssubscriptionfilters\": { \"SingularDisplayName\": \"Logs Subscription Filter\" }\r\n ,\"microsoft.awsconnector/macie2jobsummaries\": { \"SingularDisplayName\": \"Macie2 Job Summary\" }\r\n ,\"microsoft.awsconnector/macieallowlists\": { \"SingularDisplayName\": \"Macie Allow List\" }\r\n ,\"microsoft.awsconnector/networkfirewallfirewallpolicies\": { \"SingularDisplayName\": \"Network Firewall Firewall Policy\" }\r\n ,\"microsoft.awsconnector/networkfirewallfirewalls\": { \"SingularDisplayName\": \"Network Firewall Firewall\" }\r\n ,\"microsoft.awsconnector/networkfirewallrulegroups\": { \"SingularDisplayName\": \"Network Firewall Rule Group\" }\r\n ,\"microsoft.awsconnector/opensearchdomainstatuses\": { \"SingularDisplayName\": \"Open Search Domain Status\" }\r\n ,\"microsoft.awsconnector/opensearchservicedomains\": { \"SingularDisplayName\": \"Open Search Service Domain\" }\r\n ,\"microsoft.awsconnector/organizationsaccounts\": { \"SingularDisplayName\": \"Organizations Account\" }\r\n ,\"microsoft.awsconnector/organizationsorganizations\": { \"SingularDisplayName\": \"Organizations Organization\" }\r\n ,\"microsoft.awsconnector/rdsdbclusters\": { \"SingularDisplayName\": \"RDS DBCluster\" }\r\n ,\"microsoft.awsconnector/rdsdbinstances\": { \"SingularDisplayName\": \"RDS DBInstance\" }\r\n ,\"microsoft.awsconnector/rdsdbsnapshotattributesresults\": { \"SingularDisplayName\": \"RDS DBSnapshot Attributes Result\" }\r\n ,\"microsoft.awsconnector/rdsdbsnapshots\": { \"SingularDisplayName\": \"RDS DBSnapshot\" }\r\n ,\"microsoft.awsconnector/rdseventsubscriptions\": { \"SingularDisplayName\": \"RDS Event Subscription\" }\r\n ,\"microsoft.awsconnector/rdsexporttasks\": { \"SingularDisplayName\": \"RDS Export Task\" }\r\n ,\"microsoft.awsconnector/redshiftclusterparametergroups\": { \"SingularDisplayName\": \"Redshift Cluster Parameter Group\" }\r\n ,\"microsoft.awsconnector/redshiftclusters\": { \"SingularDisplayName\": \"Redshift Cluster\" }\r\n ,\"microsoft.awsconnector/route53domainsdomainsummaries\": { \"SingularDisplayName\": \"Route 53 Domains Domain Summary\" }\r\n ,\"microsoft.awsconnector/route53hostedzones\": { \"SingularDisplayName\": \"Route53 Hosted Zone\" }\r\n ,\"microsoft.awsconnector/route53resourcerecordsets\": { \"SingularDisplayName\": \"Route 53 Resource Record Set\" }\r\n ,\"microsoft.awsconnector/s3accesscontrolpolicies\": { \"SingularDisplayName\": \"S3 Access Control Policy\" }\r\n ,\"microsoft.awsconnector/s3accesspoints\": { \"SingularDisplayName\": \"S3 Access Point\" }\r\n ,\"microsoft.awsconnector/s3bucketpolicies\": { \"SingularDisplayName\": \"S3 Bucket Policy\" }\r\n ,\"microsoft.awsconnector/s3buckets\": { \"SingularDisplayName\": \"S3 Bucket\" }\r\n ,\"microsoft.awsconnector/s3controlmultiregionaccesspointpolicydocuments\": { \"SingularDisplayName\": \"S3 Control Multi Region Access Point Policy Document\" }\r\n ,\"microsoft.awsconnector/sagemakerapps\": { \"SingularDisplayName\": \"Sage Maker App\" }\r\n ,\"microsoft.awsconnector/sagemakerdevices\": { \"SingularDisplayName\": \"Sage Maker Device\" }\r\n ,\"microsoft.awsconnector/sagemakerimages\": { \"SingularDisplayName\": \"Sage Maker Image\" }\r\n ,\"microsoft.awsconnector/sagemakernotebookinstancesummaries\": { \"SingularDisplayName\": \"Sage Maker Notebook Instance Summary\" }\r\n ,\"microsoft.awsconnector/secretsmanagerresourcepolicies\": { \"SingularDisplayName\": \"Secrets Manager Resource Policy\" }\r\n ,\"microsoft.awsconnector/secretsmanagersecrets\": { \"SingularDisplayName\": \"Secrets Manager Secret\" }\r\n ,\"microsoft.awsconnector/snssubscriptions\": { \"SingularDisplayName\": \"SNS Subscription\" }\r\n ,\"microsoft.awsconnector/snstopics\": { \"SingularDisplayName\": \"SNS Topic\" }\r\n ,\"microsoft.awsconnector/sqsqueues\": { \"SingularDisplayName\": \"SQS Queue\" }\r\n ,\"microsoft.awsconnector/ssminstanceinformations\": { \"SingularDisplayName\": \"SSM Instance Information\" }\r\n ,\"microsoft.awsconnector/ssmparameters\": { \"SingularDisplayName\": \"SSM Parameter\" }\r\n ,\"microsoft.awsconnector/ssmresourcecompliancesummaryitems\": { \"SingularDisplayName\": \"SSM Resource Compliance Summary Item\" }\r\n ,\"microsoft.awsconnector/wafv2ipsets\": { \"SingularDisplayName\": \"WAFv2 IPSet\" }\r\n ,\"microsoft.awsconnector/wafv2loggingconfigurations\": { \"SingularDisplayName\": \"WAFv2 Logging Configuration\" }\r\n ,\"microsoft.awsconnector/wafv2webaclassociations\": { \"SingularDisplayName\": \"WAFv2 Web ACLAssociation\" }\r\n ,\"microsoft.awsconnector/wafwebaclsummaries\": { \"SingularDisplayName\": \"WAF Web ACLSummary\" }\r\n ,\"microsoft.azureactivedirectory/b2cdirectories\": { \"SingularDisplayName\": \"B2C tenant\" }\r\n ,\"microsoft.azureactivedirectory/ciamdirectories\": { \"SingularDisplayName\": \"External Configuration Tenant\" }\r\n ,\"microsoft.azureactivedirectory/guestusages\": { \"SingularDisplayName\": \"Guest Usage\" }\r\n ,\"microsoft.azurearcdata/datacontrollers\": { \"SingularDisplayName\": \"Azure Arc data controller\" }\r\n ,\"microsoft.azurearcdata/mysqlserver\": { \"SingularDisplayName\": \"MySql Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/postgresinstances\": { \"SingularDisplayName\": \"PostgreSQL server ? Azure Arc\" }\r\n ,\"microsoft.azurearcdata/postgressqlserver\": { \"SingularDisplayName\": \"PostgresSql Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlmanagedinstances\": { \"SingularDisplayName\": \"SQL managed instance - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserveresulicenses\": { \"SingularDisplayName\": \"SQL Server ESU license\" }\r\n ,\"microsoft.azurearcdata/sqlserverinstances\": { \"SingularDisplayName\": \"SQL Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserverinstances/databases\": { \"SingularDisplayName\": \"SQL Server database - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserverlicenses\": { \"SingularDisplayName\": \"SQL Server License\" }\r\n ,\"microsoft.azurebusinesscontinuity/deletedunifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity deleted unified protected item\" }\r\n ,\"microsoft.azurebusinesscontinuity/unifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity unified protected item\" }\r\n ,\"microsoft.azurecis/aadapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis AAD application\" }\r\n ,\"microsoft.azurecis/addressrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis address record\" }\r\n ,\"microsoft.azurecis/autopilotenvironments\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot environment\" }\r\n ,\"microsoft.azurecis/autopilotmachinefunctions\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot machine function\" }\r\n ,\"microsoft.azurecis/autopilotsoftwareloadbalancevirtualips\": { \"SingularDisplayName\": \"Microsoft.AzureCis auto pilot software load balance virtual IP\" }\r\n ,\"microsoft.azurecis/azcopies\": { \"SingularDisplayName\": \"Microsoft.AzureCis az copy\" }\r\n ,\"microsoft.azurecis/canonicalnamerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis canonical name record\" }\r\n ,\"microsoft.azurecis/dsmsallowlists\": { \"SingularDisplayName\": \"Microsoft.AzureCis ds msallowlist\" }\r\n ,\"microsoft.azurecis/dsmscertificates\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms certificate\" }\r\n ,\"microsoft.azurecis/dsmsrootfolders\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms root folder\" }\r\n ,\"microsoft.azurecis/dstsapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts application\" }\r\n ,\"microsoft.azurecis/dstsserviceaccounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service account\" }\r\n ,\"microsoft.azurecis/dstsserviceclientidentities\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service client identity\" }\r\n ,\"microsoft.azurecis/genericgenevaactions\": { \"SingularDisplayName\": \"Microsoft.AzureCis generic geneva action\" }\r\n ,\"microsoft.azurecis/plannedquotas\": { \"SingularDisplayName\": \"Microsoft.AzureCis planned quota\" }\r\n ,\"microsoft.azurecis/pointerrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis pointer record\" }\r\n ,\"microsoft.azurecis/publishconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis publish config value\" }\r\n ,\"microsoft.azurecis/pushagentv2accounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis push agent v2 account\" }\r\n ,\"microsoft.azurecis/servicerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis service record\" }\r\n ,\"microsoft.azurecis/sharedconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis shared config value\" }\r\n ,\"microsoft.azurecloudmetadata/clouds\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata cloud\" }\r\n ,\"microsoft.azurecloudmetadata/clouds/geographies\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geography\" }\r\n ,\"microsoft.azurecloudmetadata/clouds/geographies/regions\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geographies region\" }\r\n ,\"microsoft.azuredatatransfer/connections\": { \"SingularDisplayName\": \"Connection\" }\r\n ,\"microsoft.azuredatatransfer/connections/flows\": { \"SingularDisplayName\": \"Flow\" }\r\n ,\"microsoft.azuredatatransfer/pipelines\": { \"SingularDisplayName\": \"Pipeline\" }\r\n ,\"microsoft.azurefleet/fleets\": { \"SingularDisplayName\": \"Compute Fleet\" }\r\n ,\"microsoft.azurefleet/fleetscomputehub\": { \"SingularDisplayName\": \"Compute Fleet\" }\r\n ,\"microsoft.azureimagetestingforlinux/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job\" }\r\n ,\"microsoft.azureimagetestingforlinux/jobtemplates\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job template\" }\r\n ,\"microsoft.azurelargeinstance/azurelargeinstances\": { \"SingularDisplayName\": \"Azure Large Instance\" }\r\n ,\"microsoft.azurelargeinstance/azurelargestorageinstances\": { \"SingularDisplayName\": \"Microsoft.AzureLargeInstance Azure large storage instance\" }\r\n ,\"microsoft.azurepercept/accounts\": { \"SingularDisplayName\": \"Microsoft.AzurePercept account\" }\r\n ,\"microsoft.azurepercept/accounts/devices\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts device\" }\r\n ,\"microsoft.azurepercept/accounts/devices/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts devices sensor\" }\r\n ,\"microsoft.azurepercept/accounts/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts sensor\" }\r\n ,\"microsoft.azurepercept/accounts/solutioninstances\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solutioninstance\" }\r\n ,\"microsoft.azurepercept/accounts/solutions\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solution\" }\r\n ,\"microsoft.azurepercept/accounts/targets\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts target\" }\r\n ,\"microsoft.azureplaywrightservice/accounts\": { \"SingularDisplayName\": \"Playwright Testing\" }\r\n ,\"microsoft.azurescan/scanningaccounts\": { \"SingularDisplayName\": \"ESRP Scan\" }\r\n ,\"microsoft.azuresphere/catalogs\": { \"SingularDisplayName\": \"Azure Sphere Catalog\" }\r\n ,\"microsoft.azurespherev2/catalogs\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalog\" }\r\n ,\"microsoft.azurespherev2/catalogs/artifacts\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs artifact\" }\r\n ,\"microsoft.azurespherev2/catalogs/certificates\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs certificate\" }\r\n ,\"microsoft.azurespherev2/catalogs/deviceregistrations\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs device registration\" }\r\n ,\"microsoft.azurespherev2/catalogs/provisioningpackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs provisioning package\" }\r\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channel\" }\r\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels/deployments\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channels deployment\" }\r\n ,\"microsoft.azurespherev2/catalogs/updatepackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs update package\" }\r\n ,\"microsoft.azurestack/cloudmanifestfiles\": { \"SingularDisplayName\": \"Microsoft.AzureStack cloud manifest file\" }\r\n ,\"microsoft.azurestack/linkedsubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack linked subscription\" }\r\n ,\"microsoft.azurestack/registrations\": { \"SingularDisplayName\": \"Microsoft.AzureStack registration\" }\r\n ,\"microsoft.azurestack/registrations/customersubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations customer subscription\" }\r\n ,\"microsoft.azurestack/registrations/products\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations product\" }\r\n ,\"microsoft.azurestackhci/clusters\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/clusters/updates/updateruns\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/clusters/updatesummaries\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/devicepools\": { \"SingularDisplayName\": \"Azure Stack\" }\r\n ,\"microsoft.azurestackhci/edgedevices\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge device\" }\r\n ,\"microsoft.azurestackhci/edgedevices/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge devices job\" }\r\n ,\"microsoft.azurestackhci/edgemachines\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machine\" }\r\n ,\"microsoft.azurestackhci/edgemachines/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machines job\" }\r\n ,\"microsoft.azurestackhci/edgenodepools\": { \"SingularDisplayName\": \"Azure Stack\" }\r\n ,\"microsoft.azurestackhci/galleryimages\": { \"SingularDisplayName\": \"Azure Local Gallery image\" }\r\n ,\"microsoft.azurestackhci/logicalnetworks\": { \"SingularDisplayName\": \"Azure Local Logical network\" }\r\n ,\"microsoft.azurestackhci/marketplacegalleryimages\": { \"SingularDisplayName\": \"Azure Local Marketplace Gallery image\" }\r\n ,\"microsoft.azurestackhci/networkinterfaces\": { \"SingularDisplayName\": \"Azure Local VM Network Interface\" }\r\n ,\"microsoft.azurestackhci/networksecuritygroups\": { \"SingularDisplayName\": \"Azure Local Network Security Group\" }\r\n ,\"microsoft.azurestackhci/networksecuritygroups/securityrules\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI network security groups security rule\" }\r\n ,\"microsoft.azurestackhci/storagecontainers\": { \"SingularDisplayName\": \"Azure Local Storage path\" }\r\n ,\"microsoft.azurestackhci/virtualharddisks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual hard disk\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instance\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances guest agent\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.azurestackhci/virtualmachines\": { \"SingularDisplayName\": \"Azure Local virtual machine - Azure Arc\" }\r\n ,\"microsoft.azurestackhci/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual network\" }\r\n ,\"microsoft.backupsolutions/vmwareapplications\": { \"SingularDisplayName\": \"Microsoft.BackupSolutions vmware application\" }\r\n ,\"microsoft.bakeryhybrid/pies\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid py\" }\r\n ,\"microsoft.bakeryhybrid/pies/nestedresourcetype\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid pies nested resource type\" }\r\n ,\"microsoft.baremetal/baremetalconnections\": { \"SingularDisplayName\": \"Microsoft.BareMetal bare metal connection\" }\r\n ,\"microsoft.baremetal/crayservers\": { \"SingularDisplayName\": \"Cray Server\" }\r\n ,\"microsoft.baremetal/monitoringservers\": { \"SingularDisplayName\": \"Monitoring Server\" }\r\n ,\"microsoft.baremetal/peeringsettings\": { \"SingularDisplayName\": \"Microsoft.BareMetal peering setting\" }\r\n ,\"microsoft.baremetalinfrastructure/baremetalinstances\": { \"SingularDisplayName\": \"BareMetal Instance\" }\r\n ,\"microsoft.baremetalinfrastructure/baremetalstorageinstances\": { \"SingularDisplayName\": \"Microsoft.BareMetalInfrastructure bare metal storage instance\" }\r\n ,\"microsoft.batch/batchaccounts\": { \"SingularDisplayName\": \"Batch account\" }\r\n ,\"microsoft.billing/billingaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing account\" }\r\n ,\"microsoft.billing/billingaccounts/agreements\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts agreement\" }\r\n ,\"microsoft.billing/billingaccounts/associatedtenants\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts associated tenant\" }\r\n ,\"microsoft.billing/billingaccounts/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts available balance\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profile\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles available balance\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers transfer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/instructions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles instruction\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice section\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections product\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections transfer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/paymentmethodlinks\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles payment method link\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles policy\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/transactions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles transaction\" }\r\n ,\"microsoft.billing/billingaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptionaliases\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription aliase\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptions/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscriptions invoice\" }\r\n ,\"microsoft.billing/billingaccounts/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customer\" }\r\n ,\"microsoft.billing/billingaccounts/customers/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/customers/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers policy\" }\r\n ,\"microsoft.billing/billingaccounts/customers/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers product\" }\r\n ,\"microsoft.billing/billingaccounts/departments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts department\" }\r\n ,\"microsoft.billing/billingaccounts/departments/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/departments/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/departments/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments enrollment account\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment account\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\r\n ,\"microsoft.billing/billingaccounts/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\r\n ,\"microsoft.billing/billingaccounts/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice section\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections product\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections transfer\" }\r\n ,\"microsoft.billing/billingaccounts/lineofcredit\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts line of credit\" }\r\n ,\"microsoft.billing/billingaccounts/migrations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts migration\" }\r\n ,\"microsoft.billing/billingaccounts/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts payment method\" }\r\n ,\"microsoft.billing/billingaccounts/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts policy\" }\r\n ,\"microsoft.billing/billingaccounts/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts product\" }\r\n ,\"microsoft.billing/billingaccounts/reservationorders\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation order\" }\r\n ,\"microsoft.billing/billingaccounts/reservationorders/reservations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation orders reservation\" }\r\n ,\"microsoft.billing/billingaccounts/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\r\n ,\"microsoft.billing/billingaccounts/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\r\n ,\"microsoft.billing/billingperiods\": { \"SingularDisplayName\": \"Microsoft.Billing billing period\" }\r\n ,\"microsoft.billing/billingproperty\": { \"SingularDisplayName\": \"Microsoft.Billing billing property\" }\r\n ,\"microsoft.billing/billingrequests\": { \"SingularDisplayName\": \"Microsoft.Billing billing request\" }\r\n ,\"microsoft.billing/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing role assignment\" }\r\n ,\"microsoft.billing/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing role definition\" }\r\n ,\"microsoft.billing/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing enrollment account\" }\r\n ,\"microsoft.billing/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing payment method\" }\r\n ,\"microsoft.billing/policies\": { \"SingularDisplayName\": \"Microsoft.Billing policy\" }\r\n ,\"microsoft.billing/promotions\": { \"SingularDisplayName\": \"Microsoft.Billing promotion\" }\r\n ,\"microsoft.billing/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing transfer\" }\r\n ,\"microsoft.billingbenefits/credits\": { \"SingularDisplayName\": \"Credit\" }\r\n ,\"microsoft.billingbenefits/discounts\": { \"SingularDisplayName\": \"Discount\" }\r\n ,\"microsoft.billingbenefits/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\r\n ,\"microsoft.billingbenefits/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\r\n ,\"microsoft.billingbenefits/maccs\": { \"SingularDisplayName\": \"Microsoft Azure Consumption Commitment\" }\r\n ,\"microsoft.billingbenefits/reservationorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits reservation order aliase\" }\r\n ,\"microsoft.billingbenefits/savingsplanorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits savings plan order aliase\" }\r\n ,\"microsoft.billingbenefits/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\r\n ,\"microsoft.billingbenefits/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\r\n ,\"microsoft.bing/accounts\": { \"SingularDisplayName\": \"Bing Resource\" }\r\n ,\"microsoft.blockchain/blockchainmembers\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain member\" }\r\n ,\"microsoft.blockchain/blockchainmembers/transactionnodes\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain members transaction node\" }\r\n ,\"microsoft.blockchaintokens/tokenservices\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token service\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/blockchainnetworks\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services blockchain network\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/groups\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services group\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/groups/accounts\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services groups account\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/tokentemplates\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services token template\" }\r\n ,\"microsoft.bluefin/instances\": { \"SingularDisplayName\": \"Microsoft.Bluefin instance\" }\r\n ,\"microsoft.bluefin/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances dataset\" }\r\n ,\"microsoft.bluefin/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances pipeline\" }\r\n ,\"microsoft.blueprint/blueprintassignments\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint assignment\" }\r\n ,\"microsoft.blueprint/blueprints\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint\" }\r\n ,\"microsoft.blueprint/blueprints/artifacts\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints artifact\" }\r\n ,\"microsoft.blueprint/blueprints/versions\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints version\" }\r\n ,\"microsoft.botservice/botservices\": { \"SingularDisplayName\": \"Bot Service\" }\r\n ,\"microsoft.cache/redis\": { \"SingularDisplayName\": \"Redis cache\" }\r\n ,\"microsoft.cache/redisenterprise\": { \"SingularDisplayName\": \"Azure Managed Redis\" }\r\n ,\"microsoft.cache/redisenterprise/databases\": { \"SingularDisplayName\": \"Redis Enterprise database\" }\r\n ,\"microsoft.capacity/reservationorders\": { \"SingularDisplayName\": \"Reservation order\" }\r\n ,\"microsoft.capacity/reservationorders/reservations\": { \"SingularDisplayName\": \"Reservation\" }\r\n ,\"microsoft.cascade/sites\": { \"SingularDisplayName\": \"Microsoft.Cascade site\" }\r\n ,\"microsoft.cdn/cdnwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Content Delivery Network WAF policy\" }\r\n ,\"microsoft.cdn/edgeactions\": { \"SingularDisplayName\": \"Edge Action\" }\r\n ,\"microsoft.cdn/profiles\": { \"SingularDisplayName\": \"Front Door and CDN profile\" }\r\n ,\"microsoft.cdn/profiles/afdendpoints\": { \"SingularDisplayName\": \"Endpoint\" }\r\n ,\"microsoft.cdn/profiles/afdendpoints/routes\": { \"SingularDisplayName\": \"Route\" }\r\n ,\"microsoft.cdn/profiles/customdomains\": { \"SingularDisplayName\": \"Custom domain\" }\r\n ,\"microsoft.cdn/profiles/endpoints\": { \"SingularDisplayName\": \"CDN endpoint\" }\r\n ,\"microsoft.cdn/profiles/endpoints/customdomains\": { \"SingularDisplayName\": \"CDN custom domain\" }\r\n ,\"microsoft.cdn/profiles/endpoints/origins\": { \"SingularDisplayName\": \"CDN origin\" }\r\n ,\"microsoft.cdn/profiles/origingroups\": { \"SingularDisplayName\": \"Origin group\" }\r\n ,\"microsoft.cdn/profiles/origingroups/origins\": { \"SingularDisplayName\": \"Origin\" }\r\n ,\"microsoft.cdn/profiles/rulesets\": { \"SingularDisplayName\": \"Rule set\" }\r\n ,\"microsoft.cdn/profiles/rulesets/rules\": { \"SingularDisplayName\": \"Rule\" }\r\n ,\"microsoft.cdn/profiles/secrets\": { \"SingularDisplayName\": \"Secret\" }\r\n ,\"microsoft.cdn/profiles/securitypolicies\": { \"SingularDisplayName\": \"Security policy\" }\r\n ,\"microsoft.certificateregistration/certificateorders\": { \"SingularDisplayName\": \"App Service certificate\" }\r\n ,\"microsoft.certify/testsuites\": { \"SingularDisplayName\": \"Microsoft.Certify test suite\" }\r\n ,\"microsoft.certify/validationjobs\": { \"SingularDisplayName\": \"Microsoft.Certify validation job\" }\r\n ,\"microsoft.changeanalysis/profile\": { \"SingularDisplayName\": \"Microsoft.ChangeAnalysis profile\" }\r\n ,\"microsoft.changesafety/changestates\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change state\" }\r\n ,\"microsoft.changesafety/changestates/stageprogressions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change states stage progression\" }\r\n ,\"microsoft.changesafety/stagemaps\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety stage map\" }\r\n ,\"microsoft.changesafety/validations\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validation\" }\r\n ,\"microsoft.changesafety/validators\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validator\" }\r\n ,\"microsoft.changesafety/validators/versions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validators version\" }\r\n ,\"microsoft.chaos/experiments\": { \"SingularDisplayName\": \"Chaos Experiment\" }\r\n ,\"microsoft.chaos/privateaccesses\": { \"SingularDisplayName\": \"Agent Private Access\" }\r\n ,\"microsoft.chaos/targets\": { \"SingularDisplayName\": \"Microsoft.Chaos target\" }\r\n ,\"microsoft.chaos/targets/capabilities\": { \"SingularDisplayName\": \"Microsoft.Chaos targets capability\" }\r\n ,\"microsoft.classiccompute/domainnames\": { \"SingularDisplayName\": \"Cloud service (classic)\" }\r\n ,\"microsoft.classiccompute/domainnames/slots/roles\": { \"SingularDisplayName\": \"Cloud service role (classic)\" }\r\n ,\"microsoft.classiccompute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine (classic)\" }\r\n ,\"microsoft.classicnetwork/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group (classic)\" }\r\n ,\"microsoft.classicnetwork/reservedips\": { \"SingularDisplayName\": \"Reserved IP address (classic)\" }\r\n ,\"microsoft.classicnetwork/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network (classic)\" }\r\n })[tolower(id)]\r\n}\r\n", - "$fxv#1": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_2(id: string) {\r\n dynamic({\r\n \"microsoft.classicstorage/storageaccounts\": { \"SingularDisplayName\": \"Storage account (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/disks\": { \"SingularDisplayName\": \"Disk (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/osimages\": { \"SingularDisplayName\": \"OS image (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/vmimages\": { \"SingularDisplayName\": \"VM image (classic)\" }\r\n ,\"microsoft.cleanroom/cleanrooms\": { \"SingularDisplayName\": \"Microsoft.CleanRoom cleanroom\" }\r\n ,\"microsoft.cleanroom/collaborations\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaboration\" }\r\n ,\"microsoft.cleanroom/collaborations/contracts\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaborations contract\" }\r\n ,\"microsoft.cleanroom/consortiums\": { \"SingularDisplayName\": \"Microsoft.CleanRoom consortium\" }\r\n ,\"microsoft.cleanroom/microservices\": { \"SingularDisplayName\": \"Microsoft.CleanRoom microservice\" }\r\n ,\"microsoft.cloud/hubs\": { \"SingularDisplayName\": \"FinOps hub\" }\r\n ,\"microsoft.clouddeviceplatform/delegatedidentities\": { \"SingularDisplayName\": \"Microsoft.CloudDevicePlatform delegated identity\" }\r\n ,\"microsoft.cloudhealth/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\r\n ,\"microsoft.cloudtest/accounts\": { \"SingularDisplayName\": \"CloudTest Account\" }\r\n ,\"microsoft.cloudtest/buildcaches\": { \"SingularDisplayName\": \"1ES Build Cache\" }\r\n ,\"microsoft.cloudtest/hostedpools\": { \"SingularDisplayName\": \"1ES Hosted Pool\" }\r\n ,\"microsoft.cloudtest/images\": { \"SingularDisplayName\": \"1ES Image\" }\r\n ,\"microsoft.cloudtest/pools\": { \"SingularDisplayName\": \"CloudTest Pool\" }\r\n ,\"microsoft.clusterstor/nodes\": { \"SingularDisplayName\": \"ClusterStor\" }\r\n ,\"microsoft.codesigning/codesigningaccounts\": { \"SingularDisplayName\": \"Trusted Signing Account\" }\r\n ,\"microsoft.codespaces/plans\": { \"SingularDisplayName\": \"Microsoft.Codespaces plan\" }\r\n ,\"microsoft.cognitiveservices/accounts\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.cognitiveservices/accounts/projects\": { \"SingularDisplayName\": \"Azure AI Foundry project\" }\r\n ,\"microsoft.cognitiveservices/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plan\" }\r\n ,\"microsoft.cognitiveservices/commitmentplans/accountassociations\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plans account association\" }\r\n ,\"microsoft.communication/communicationservices\": { \"SingularDisplayName\": \"Communication Service\" }\r\n ,\"microsoft.communication/emailservices\": { \"SingularDisplayName\": \"Email Communication Service\" }\r\n ,\"microsoft.communication/emailservices/domains\": { \"SingularDisplayName\": \"Email Communication Services Domain\" }\r\n ,\"microsoft.community/communitytrainings\": { \"SingularDisplayName\": \"Community Training\" }\r\n ,\"microsoft.compositesolutions/compositesolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution definition\" }\r\n ,\"microsoft.compositesolutions/compositesolutions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution\" }\r\n ,\"microsoft.compute/availabilitysets\": { \"SingularDisplayName\": \"Availability set\" }\r\n ,\"microsoft.compute/capacityreservationgroups\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\r\n ,\"microsoft.compute/capacityreservationgroups/capacityreservations\": { \"SingularDisplayName\": \"Capacity reservation\" }\r\n ,\"microsoft.compute/capacityreservationgroupscomputehub\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\r\n ,\"microsoft.compute/cloudservices\": { \"SingularDisplayName\": \"Cloud service (extended support)\" }\r\n ,\"microsoft.compute/computefleetinstances\": { \"SingularDisplayName\": \"Instance\" }\r\n ,\"microsoft.compute/computefleetscalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.compute/diskaccesses\": { \"SingularDisplayName\": \"Disk Access\" }\r\n ,\"microsoft.compute/diskencryptionsets\": { \"SingularDisplayName\": \"Disk Encryption Set\" }\r\n ,\"microsoft.compute/disks\": { \"SingularDisplayName\": \"Disk\" }\r\n ,\"microsoft.compute/galleries\": { \"SingularDisplayName\": \"Azure compute gallery\" }\r\n ,\"microsoft.compute/galleries/applications\": { \"SingularDisplayName\": \"VM application definition\" }\r\n ,\"microsoft.compute/galleries/applications/versions\": { \"SingularDisplayName\": \"VM application version\" }\r\n ,\"microsoft.compute/galleries/images\": { \"SingularDisplayName\": \"VM image definition\" }\r\n ,\"microsoft.compute/galleries/images/versions\": { \"SingularDisplayName\": \"VM image version\" }\r\n ,\"microsoft.compute/galleries/imagescomputehub\": { \"SingularDisplayName\": \"VM image definition\" }\r\n ,\"microsoft.compute/hostgroups\": { \"SingularDisplayName\": \"Host group\" }\r\n ,\"microsoft.compute/hostgroups/hosts\": { \"SingularDisplayName\": \"Host\" }\r\n ,\"microsoft.compute/hostgroupscomputehub\": { \"SingularDisplayName\": \"Host group\" }\r\n ,\"microsoft.compute/images\": { \"SingularDisplayName\": \"Image\" }\r\n ,\"microsoft.compute/imagescomputehub\": { \"SingularDisplayName\": \"Image\" }\r\n ,\"microsoft.compute/locations/communitygalleries/images\": { \"SingularDisplayName\": \"Community image\" }\r\n ,\"microsoft.compute/locations/communitygalleries/imagescomputehub\": { \"SingularDisplayName\": \"Community image\" }\r\n ,\"microsoft.compute/proximityplacementgroups\": { \"SingularDisplayName\": \"Proximity placement group\" }\r\n ,\"microsoft.compute/proximityplacementgroupscomputehub\": { \"SingularDisplayName\": \"Proximity placement group\" }\r\n ,\"microsoft.compute/restorepointcollections\": { \"SingularDisplayName\": \"Restore Point Collection\" }\r\n ,\"microsoft.compute/restorepointcollections/restorepoints\": { \"SingularDisplayName\": \"Restore Point\" }\r\n ,\"microsoft.compute/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\r\n ,\"microsoft.compute/sshpublickeys\": { \"SingularDisplayName\": \"SSH key\" }\r\n ,\"microsoft.compute/standbypoolinstance\": { \"SingularDisplayName\": \"Standby pool\" }\r\n ,\"microsoft.compute/virtualmachinecomputehub\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.compute/virtualmachineflexinstances\": { \"SingularDisplayName\": \"Instance\" }\r\n ,\"microsoft.compute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.compute/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.compute/virtualmachinescalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine scale set instance\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines/networkinterfaces/ipconfigurations/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\r\n ,\"microsoft.compute/virtualmachinescalesetscomputehub\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.computehub/advisorcost\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisoroperationalexcellence\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorperformance\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorreliability\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorsecurity\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/all\": { \"SingularDisplayName\": \"All resources\" }\r\n ,\"microsoft.computehub/backup\": { \"SingularDisplayName\": \"Backup job\" }\r\n ,\"microsoft.computehub/computehubmain\": { \"SingularDisplayName\": \"Compute infrastructure\" }\r\n ,\"microsoft.computehub/healthevents\": { \"SingularDisplayName\": \"Health events\" }\r\n ,\"microsoft.computehub/linuxostype\": { \"SingularDisplayName\": \"Linux OS\" }\r\n ,\"microsoft.computehub/microsoftdefenderfreetrialsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\r\n ,\"microsoft.computehub/microsoftdefenderstandardsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\r\n ,\"microsoft.computehub/outages\": { \"SingularDisplayName\": \"Outages\" }\r\n ,\"microsoft.computehub/powerstatedeallocated\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/powerstaterunning\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/powerstatestopped\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/provisioningstatefailedresources\": { \"SingularDisplayName\": \"Provisioning states\" }\r\n ,\"microsoft.computehub/provisioningstatesucceededresources\": { \"SingularDisplayName\": \"Provisioning states\" }\r\n ,\"microsoft.computehub/windowsostype\": { \"SingularDisplayName\": \"Windows OS\" }\r\n ,\"microsoft.computeschedule/autoactions\": { \"SingularDisplayName\": \"Automatic Action\" }\r\n ,\"microsoft.computeschedule/autoactions/occurrences\": { \"SingularDisplayName\": \"Microsoft.ComputeSchedule auto actions occurrence\" }\r\n ,\"microsoft.confidentialledger/ledgers\": { \"SingularDisplayName\": \"Confidential Ledger\" }\r\n ,\"microsoft.confidentialledger/managedccfs\": { \"SingularDisplayName\": \"Managed CCF App\" }\r\n ,\"microsoft.confluent/agreements\": { \"SingularDisplayName\": \"Microsoft.Confluent agreement\" }\r\n ,\"microsoft.confluent/organizations\": { \"SingularDisplayName\": \"Confluent organization\" }\r\n ,\"microsoft.connectedcache/cachenodes\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\r\n ,\"microsoft.connectedcache/enterprisecustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\r\n ,\"microsoft.connectedcache/enterprisemcccustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\r\n ,\"microsoft.connectedcache/enterprisemcccustomers/enterprisemcccachenodes\": { \"SingularDisplayName\": \"MCC CacheNode for Enterprise\" }\r\n ,\"microsoft.connectedcache/ispcustomers\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\r\n ,\"microsoft.connectedcredentials/credentials\": { \"SingularDisplayName\": \"Microsoft.ConnectedCredentials credential\" }\r\n ,\"microsoft.connectedvehicle/platformaccounts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVehicle platform account\" }\r\n ,\"microsoft.connectedvmwarevsphere/clusters\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere cluster\" }\r\n ,\"microsoft.connectedvmwarevsphere/datastores\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere datastore\" }\r\n ,\"microsoft.connectedvmwarevsphere/hosts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere host\" }\r\n ,\"microsoft.connectedvmwarevsphere/resourcepools\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere resource pool\" }\r\n ,\"microsoft.connectedvmwarevsphere/vcenters\": { \"SingularDisplayName\": \"VMware vCenter\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instance\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances guest agent\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachines\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine template\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual network\" }\r\n ,\"microsoft.consumption/budgets\": { \"SingularDisplayName\": \"Microsoft.Consumption budget\" }\r\n ,\"microsoft.consumption/credits\": { \"SingularDisplayName\": \"Microsoft.Consumption credit\" }\r\n ,\"microsoft.consumption/pricesheets\": { \"SingularDisplayName\": \"Microsoft.Consumption pricesheet\" }\r\n ,\"microsoft.containerinstance/containergroupprofiles\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profile\" }\r\n ,\"microsoft.containerinstance/containergroupprofiles/revisions\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profiles revision\" }\r\n ,\"microsoft.containerinstance/containergroups\": { \"SingularDisplayName\": \"Container instances\" }\r\n ,\"microsoft.containerinstance/ngroups\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance ngroup\" }\r\n ,\"microsoft.containerregistry/registries\": { \"SingularDisplayName\": \"Container registry\" }\r\n ,\"microsoft.containerregistry/registries/replications\": { \"SingularDisplayName\": \"Container registry replication\" }\r\n ,\"microsoft.containerregistry/registries/scopemaps\": { \"SingularDisplayName\": \"Container registry scope map\" }\r\n ,\"microsoft.containerregistry/registries/tokens\": { \"SingularDisplayName\": \"Container registry token\" }\r\n ,\"microsoft.containerregistry/registries/webhooks\": { \"SingularDisplayName\": \"Container registry webhook\" }\r\n ,\"microsoft.containerservice/fleets\": { \"SingularDisplayName\": \"Kubernetes fleet manager\" }\r\n ,\"microsoft.containerservice/managedclusters\": { \"SingularDisplayName\": \"Kubernetes service\" }\r\n ,\"microsoft.containerservice/managedclusters/managednamespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes namespace\" }\r\n ,\"microsoft.containerservice/managedclusters/namespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\r\n ,\"microsoft.containerservice/managedclustersnapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService managedclustersnapshot\" }\r\n ,\"microsoft.containerservice/snapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService snapshot\" }\r\n ,\"microsoft.containerstorage/pools\": { \"SingularDisplayName\": \"Container storage\" }\r\n ,\"microsoft.costmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.CostManagement alert\" }\r\n ,\"microsoft.costmanagement/budgets\": { \"SingularDisplayName\": \"Microsoft.CostManagement budget\" }\r\n ,\"microsoft.costmanagement/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement cloud connector\" }\r\n ,\"microsoft.costmanagement/connectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement connector\" }\r\n ,\"microsoft.costmanagement/costallocationrules\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost allocation rule\" }\r\n ,\"microsoft.costmanagement/costdetailsoperationresults\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost details operation result\" }\r\n ,\"microsoft.costmanagement/exports\": { \"SingularDisplayName\": \"Microsoft.CostManagement export\" }\r\n ,\"microsoft.costmanagement/externalbillingaccounts\": { \"SingularDisplayName\": \"Microsoft.CostManagement external billing account\" }\r\n ,\"microsoft.costmanagement/externalsubscriptions\": { \"SingularDisplayName\": \"Microsoft.CostManagement external subscription\" }\r\n ,\"microsoft.costmanagement/markuprules\": { \"SingularDisplayName\": \"Microsoft.CostManagement markup rule\" }\r\n ,\"microsoft.costmanagement/operationstatus\": { \"SingularDisplayName\": \"Microsoft.CostManagement operation statu\" }\r\n ,\"microsoft.costmanagement/reportconfigs\": { \"SingularDisplayName\": \"Microsoft.CostManagement reportconfig\" }\r\n ,\"microsoft.costmanagement/reports\": { \"SingularDisplayName\": \"Microsoft.CostManagement report\" }\r\n ,\"microsoft.costmanagement/scheduledactions\": { \"SingularDisplayName\": \"Microsoft.CostManagement scheduled action\" }\r\n ,\"microsoft.costmanagement/settings\": { \"SingularDisplayName\": \"Microsoft.CostManagement setting\" }\r\n ,\"microsoft.costmanagement/views\": { \"SingularDisplayName\": \"Microsoft.CostManagement view\" }\r\n ,\"microsoft.customerlockbox/requests\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox request\" }\r\n ,\"microsoft.customerlockbox/tenantoptedin\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox tenant opted in\" }\r\n ,\"microsoft.customproviders/associations\": { \"SingularDisplayName\": \"Microsoft.CustomProviders association\" }\r\n ,\"microsoft.customproviders/resourceproviders\": { \"SingularDisplayName\": \"Microsoft.CustomProviders resource provider\" }\r\n ,\"microsoft.dashboard/dashboards\": { \"SingularDisplayName\": \"Azure Monitor dashboards with Grafana\" }\r\n ,\"microsoft.dashboard/grafana\": { \"SingularDisplayName\": \"Azure Managed Grafana\" }\r\n ,\"microsoft.dataaccelerator/indexclusters\": { \"SingularDisplayName\": \"Microsoft.DataAccelerator index cluster\" }\r\n ,\"microsoft.databasefleetmanager/fleets\": { \"SingularDisplayName\": \"Database fleet manager\" }\r\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces\": { \"SingularDisplayName\": \"Fleetspaces\" }\r\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces/databases\": { \"SingularDisplayName\": \"Fleet managed database\" }\r\n ,\"microsoft.databasefleetmanager/fleets/tiers\": { \"SingularDisplayName\": \"tier\" }\r\n ,\"microsoft.databasewatcher/watchers\": { \"SingularDisplayName\": \"Database watcher\" }\r\n ,\"microsoft.databox/jobs\": { \"SingularDisplayName\": \"Azure Data Box\" }\r\n ,\"microsoft.databoxedge/databoxedgedevices\": { \"SingularDisplayName\": \"Azure Stack Edge / Data Box Gateway\" }\r\n ,\"microsoft.databricks/accessconnectors\": { \"SingularDisplayName\": \"Access Connector for Azure Databricks\" }\r\n ,\"microsoft.databricks/workspaces\": { \"SingularDisplayName\": \"Azure Databricks Service\" }\r\n ,\"microsoft.datacatalog/catalogs\": { \"SingularDisplayName\": \"Data catalog\" }\r\n ,\"microsoft.datacollaboration/workspaces\": { \"SingularDisplayName\": \"Project CI\" }\r\n ,\"microsoft.datadog/agreements\": { \"SingularDisplayName\": \"Microsoft.Datadog agreement\" }\r\n ,\"microsoft.datadog/monitors\": { \"SingularDisplayName\": \"Datadog\" }\r\n ,\"microsoft.datadog/subscriptionstatuses\": { \"SingularDisplayName\": \"Microsoft.Datadog subscription statuse\" }\r\n ,\"microsoft.datafactory/datafactories\": { \"SingularDisplayName\": \"Data factory\" }\r\n ,\"microsoft.datafactory/factories\": { \"SingularDisplayName\": \"Data factory (V2)\" }\r\n ,\"microsoft.datafactory/factories/pipelines\": { \"SingularDisplayName\": \"Data Factory pipeline\" }\r\n ,\"microsoft.datafactory/factories/triggers\": { \"SingularDisplayName\": \"Data Factory trigger\" }\r\n ,\"microsoft.datalakeanalytics/accounts\": { \"SingularDisplayName\": \"Data Lake Analytics account\" }\r\n ,\"microsoft.datalakestore/accounts\": { \"SingularDisplayName\": \"Data Lake Storage Gen1\" }\r\n ,\"microsoft.datamigration/databasemigrations\": { \"SingularDisplayName\": \"Microsoft.DataMigration database migration\" }\r\n ,\"microsoft.datamigration/migrationservices\": { \"SingularDisplayName\": \"Microsoft.DataMigration migration service\" }\r\n ,\"microsoft.datamigration/services\": { \"SingularDisplayName\": \"Azure Database Migration Service (classic)\" }\r\n ,\"microsoft.datamigration/services/projects\": { \"SingularDisplayName\": \"Azure Database Migration Project\" }\r\n ,\"microsoft.datamigration/sqlmigrationservices\": { \"SingularDisplayName\": \"Azure Database Migration Service\" }\r\n ,\"microsoft.dataprotection/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\r\n ,\"microsoft.dataprotection/resourceguards\": { \"SingularDisplayName\": \"Resource Guard\" }\r\n ,\"microsoft.datareplication/replicationfabrics\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabric\" }\r\n ,\"microsoft.datareplication/replicationfabrics/fabricagents\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agent\" }\r\n ,\"microsoft.datareplication/replicationfabrics/fabricagents/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agents operation\" }\r\n ,\"microsoft.datareplication/replicationfabrics/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics operation\" }\r\n ,\"microsoft.datareplication/replicationvaults\": { \"SingularDisplayName\": \"Data replication vault\" }\r\n ,\"microsoft.datareplication/replicationvaults/alertsettings\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults alert setting\" }\r\n ,\"microsoft.datareplication/replicationvaults/events\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults event\" }\r\n ,\"microsoft.datareplication/replicationvaults/jobs\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults job\" }\r\n ,\"microsoft.datareplication/replicationvaults/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults jobs operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnectionproxies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection proxy\" }\r\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection\" }\r\n ,\"microsoft.datareplication/replicationvaults/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private link resource\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected item\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems/recoverypoints\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items recovery point\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationextensions\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extension\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationextensions/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extensions operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policy\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policies operation\" }\r\n ,\"microsoft.datashare/accounts\": { \"SingularDisplayName\": \"Data Share\" }\r\n ,\"microsoft.dbformariadb/servers\": { \"SingularDisplayName\": \"Azure Database for MariaDB server\" }\r\n ,\"microsoft.dbformysql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for MySQL flexible server\" }\r\n ,\"microsoft.dbformysql/servers\": { \"SingularDisplayName\": \"MySQL server\" }\r\n ,\"microsoft.dbforpostgresql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for PostgreSQL flexible server\" }\r\n ,\"microsoft.dbforpostgresql/servergroupsv2\": { \"SingularDisplayName\": \"Azure Cosmos DB for PostgreSQL Cluster\" }\r\n ,\"microsoft.dbforpostgresql/servers\": { \"SingularDisplayName\": \"PostgreSQL server\" }\r\n ,\"microsoft.delegatednetwork/controller\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork controller\" }\r\n ,\"microsoft.delegatednetwork/delegatedsubnets\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork delegated subnet\" }\r\n ,\"microsoft.delegatednetwork/orchestrators\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork orchestrator\" }\r\n ,\"microsoft.dependencymap/maps\": { \"SingularDisplayName\": \"Microsoft.DependencyMap map\" }\r\n ,\"microsoft.dependencymap/maps/discoverysources\": { \"SingularDisplayName\": \"Microsoft.DependencyMap maps discovery source\" }\r\n ,\"microsoft.deploymentmanager/artifactsources\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager artifact source\" }\r\n ,\"microsoft.deploymentmanager/rollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topology\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies/services\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies service\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies/services/serviceunits\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies services service unit\" }\r\n ,\"microsoft.deploymentmanager/steps\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager step\" }\r\n ,\"microsoft.desktopvirtualization/appattachpackages\": { \"SingularDisplayName\": \"App attach package\" }\r\n ,\"microsoft.desktopvirtualization/applicationgroups\": { \"SingularDisplayName\": \"Application group\" }\r\n ,\"microsoft.desktopvirtualization/hostpools\": { \"SingularDisplayName\": \"Host pool\" }\r\n ,\"microsoft.desktopvirtualization/scalingplans\": { \"SingularDisplayName\": \"Scaling plan\" }\r\n ,\"microsoft.desktopvirtualization/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.devai/instances\": { \"SingularDisplayName\": \"Microsoft.DevAI instance\" }\r\n ,\"microsoft.devai/instances/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances experiment\" }\r\n ,\"microsoft.devai/instances/sandboxes\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandbox\" }\r\n ,\"microsoft.devai/instances/sandboxes/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandboxes experiment\" }\r\n ,\"microsoft.devcenter/devcenters\": { \"SingularDisplayName\": \"Dev center\" }\r\n ,\"microsoft.devcenter/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Dev Box definition\" }\r\n ,\"microsoft.devcenter/networkconnections\": { \"SingularDisplayName\": \"Network connection\" }\r\n ,\"microsoft.devcenter/plans\": { \"SingularDisplayName\": \"Dev center plan\" }\r\n ,\"microsoft.devcenter/projects\": { \"SingularDisplayName\": \"Project\" }\r\n ,\"microsoft.devcenter/projects/pools\": { \"SingularDisplayName\": \"Pool\" }\r\n ,\"microsoft.developmentwindows365/developmentcloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.DevelopmentWindows365 development cloud pc delegated msi\" }\r\n ,\"microsoft.devhub/iacprofiles\": { \"SingularDisplayName\": \"Infrastructure as Code Automation\" }\r\n ,\"microsoft.devhub/templates\": { \"SingularDisplayName\": \"Microsoft.DevHub template\" }\r\n ,\"microsoft.devhub/templates/versions\": { \"SingularDisplayName\": \"Microsoft.DevHub templates version\" }\r\n ,\"microsoft.devhub/workflows\": { \"SingularDisplayName\": \"Microsoft.DevHub workflow\" }\r\n ,\"microsoft.deviceonboarding/discoveryservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery service\" }\r\n ,\"microsoft.deviceonboarding/discoveryservices/ownershipvoucherpublickeys\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery services ownership voucher public key\" }\r\n ,\"microsoft.deviceonboarding/onboardingservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding service\" }\r\n ,\"microsoft.deviceonboarding/onboardingservices/policies\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding services policy\" }\r\n ,\"microsoft.deviceregistry/assetendpointprofiles\": { \"SingularDisplayName\": \"IoT Asset Endpoint Profile\" }\r\n ,\"microsoft.deviceregistry/assets\": { \"SingularDisplayName\": \"IoT Asset\" }\r\n ,\"microsoft.deviceregistry/billingcontainers\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry billing container\" }\r\n ,\"microsoft.deviceregistry/devices\": { \"SingularDisplayName\": \"IoT Device\" }\r\n ,\"microsoft.deviceregistry/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset\" }\r\n ,\"microsoft.deviceregistry/namespaces\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespace\" }\r\n ,\"microsoft.deviceregistry/namespaces/assetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/namespaces/assets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset\" }\r\n ,\"microsoft.deviceregistry/namespaces/devices\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces device\" }\r\n ,\"microsoft.deviceregistry/namespaces/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/namespaces/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset\" }\r\n ,\"microsoft.deviceregistry/schemaregistries\": { \"SingularDisplayName\": \"IoT Schema Registry\" }\r\n ,\"microsoft.deviceregistry/schemaregistries/schemas\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schema\" }\r\n ,\"microsoft.deviceregistry/schemaregistries/schemas/schemaversions\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schemas schema version\" }\r\n ,\"microsoft.devices/iothubs\": { \"SingularDisplayName\": \"IoT hub\" }\r\n ,\"microsoft.devices/provisioningservices\": { \"SingularDisplayName\": \"Azure IoT Hub Device Provisioning Service (DPS)\" }\r\n ,\"microsoft.deviceupdate/accounts\": { \"SingularDisplayName\": \"Device Update for IoT Hub\" }\r\n ,\"microsoft.deviceupdate/updateaccounts\": { \"SingularDisplayName\": \"Device Update Account\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/activedeployments\": { \"SingularDisplayName\": \"Device Update Active Deployment\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/agents\": { \"SingularDisplayName\": \"Device Update Agent\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/deployments\": { \"SingularDisplayName\": \"Device Update Deployment\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/deviceclasses\": { \"SingularDisplayName\": \"Device Update Device Class\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/updates\": { \"SingularDisplayName\": \"Device Update\" }\r\n ,\"microsoft.devops/pipelines\": { \"SingularDisplayName\": \"Microsoft.DevOps pipeline\" }\r\n ,\"microsoft.devopsinfrastructure/pools\": { \"SingularDisplayName\": \"Managed DevOps Pool\" }\r\n ,\"microsoft.devspaces/controllers\": { \"SingularDisplayName\": \"Microsoft.DevSpaces controller\" }\r\n ,\"microsoft.devtestlab/labs\": { \"SingularDisplayName\": \"DevTest lab\" }\r\n ,\"microsoft.devtestlab/labs/virtualmachines\": { \"SingularDisplayName\": \"DevTest Lab virtual machine\" }\r\n ,\"microsoft.devtestlab/schedules\": { \"SingularDisplayName\": \"Microsoft.DevTestLab schedule\" }\r\n ,\"microsoft.devtunnels/tunnelplans\": { \"SingularDisplayName\": \"Dev Tunnels Domain\" }\r\n ,\"microsoft.diagnostics/apollo\": { \"SingularDisplayName\": \"Microsoft.Diagnostics apollo\" }\r\n ,\"microsoft.digitaltwins/digitaltwinsinstances\": { \"SingularDisplayName\": \"Azure Digital Twins\" }\r\n ,\"microsoft.discovery/agents\": { \"SingularDisplayName\": \"Microsoft Discovery Agent\" }\r\n ,\"microsoft.discovery/bookshelves\": { \"SingularDisplayName\": \"Microsoft Discovery Bookshelf\" }\r\n ,\"microsoft.discovery/datacontainers\": { \"SingularDisplayName\": \"Microsoft Discovery Data Container\" }\r\n ,\"microsoft.discovery/datacontainers/dataassets\": { \"SingularDisplayName\": \"Data asset\" }\r\n ,\"microsoft.discovery/models\": { \"SingularDisplayName\": \"Microsoft Discovery Model\" }\r\n ,\"microsoft.discovery/storages\": { \"SingularDisplayName\": \"Microsoft Discovery Storage\" }\r\n ,\"microsoft.discovery/supercomputers\": { \"SingularDisplayName\": \"Microsoft Discovery Supercomputer\" }\r\n ,\"microsoft.discovery/supercomputers/nodepools\": { \"SingularDisplayName\": \"Nodepool\" }\r\n ,\"microsoft.discovery/tools\": { \"SingularDisplayName\": \"Microsoft Discovery Tool\" }\r\n ,\"microsoft.discovery/workflows\": { \"SingularDisplayName\": \"Microsoft Discovery Workflow\" }\r\n ,\"microsoft.discovery/workspaces\": { \"SingularDisplayName\": \"Microsoft Discovery Workspace\" }\r\n ,\"microsoft.discovery/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft Discovery Project\" }\r\n ,\"microsoft.documentdb/cassandraclusters\": { \"SingularDisplayName\": \"Azure Managed Instance for Apache Cassandra\" }\r\n ,\"microsoft.documentdb/databaseaccounts\": { \"SingularDisplayName\": \"Cosmos DB account\" }\r\n ,\"microsoft.documentdb/fleets\": { \"SingularDisplayName\": \"Azure Cosmos DB Fleet\" }\r\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccounts\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\r\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccountswithlocations\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\r\n ,\"microsoft.documentdb/mongoclusters\": { \"SingularDisplayName\": \"Azure Cosmos DB for MongoDB (vCore)\" }\r\n ,\"microsoft.documentdb/throughputpools\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pool\" }\r\n ,\"microsoft.documentdb/throughputpools/throughputpoolaccounts\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pools throughput pool account\" }\r\n ,\"microsoft.domainregistration/domains\": { \"SingularDisplayName\": \"App Service Domain\" }\r\n ,\"microsoft.domainregistration/topleveldomains\": { \"SingularDisplayName\": \"Microsoft.DomainRegistration top level domain\" }\r\n ,\"microsoft.durabletask/namespaces\": { \"SingularDisplayName\": \"Microsoft.DurableTask namespace\" }\r\n ,\"microsoft.durabletask/namespaces/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\r\n ,\"microsoft.durabletask/schedulers\": { \"SingularDisplayName\": \"Durable Task Scheduler\" }\r\n ,\"microsoft.durabletask/schedulers/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\r\n ,\"microsoft.dynamics365fraudprotection/instances\": { \"SingularDisplayName\": \"Microsoft.Dynamics365FraudProtection instance\" }\r\n ,\"microsoft.easm/workspaces\": { \"SingularDisplayName\": \"Microsoft Defender EASM\" }\r\n ,\"microsoft.edge/configurations\": { \"SingularDisplayName\": \"Site configuration\" }\r\n ,\"microsoft.edge/configurations/arcgatewayconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations arc gateway configuration\" }\r\n ,\"microsoft.edge/configurations/connectivityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations connectivity configuration\" }\r\n ,\"microsoft.edge/configurations/dynamicconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configuration\" }\r\n ,\"microsoft.edge/configurations/dynamicconfigurations/versions\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configurations version\" }\r\n ,\"microsoft.edge/configurations/networkconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations network configuration\" }\r\n ,\"microsoft.edge/configurations/securityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations security configuration\" }\r\n ,\"microsoft.edge/configurations/timeserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations time server configuration\" }\r\n ,\"microsoft.edge/connectivitystatuses\": { \"SingularDisplayName\": \"Microsoft.Edge connectivity statuse\" }\r\n ,\"microsoft.edge/disconnectedoperations\": { \"SingularDisplayName\": \"Azure Local - disconnected operations\" }\r\n ,\"microsoft.edge/siteawareresourcetypes\": { \"SingularDisplayName\": \"Microsoft.Edge site aware resource type\" }\r\n ,\"microsoft.edge/sites\": { \"SingularDisplayName\": \"Site manager - Azure Arc\" }\r\n ,\"microsoft.edge/updates\": { \"SingularDisplayName\": \"Microsoft.Edge update\" }\r\n ,\"microsoft.edgemarketplace/offers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace offer\" }\r\n ,\"microsoft.edgemarketplace/publishers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace publisher\" }\r\n ,\"microsoft.edgeorder/addresses\": { \"SingularDisplayName\": \"Azure Edge Hardware Center Address\" }\r\n ,\"microsoft.edgeorder/bootstrapconfigurations\": { \"SingularDisplayName\": \"Site Key\" }\r\n ,\"microsoft.edgeorder/orderitems\": { \"SingularDisplayName\": \"Azure Edge Hardware Center\" }\r\n ,\"microsoft.edgeorder/virtual_orderitems\": { \"SingularDisplayName\": \"Device\" }\r\n ,\"microsoft.edgezones/extendedzones\": { \"SingularDisplayName\": \"Microsoft.EdgeZones extended zone\" }\r\n ,\"microsoft.education/grants\": { \"SingularDisplayName\": \"Microsoft.Education grant\" }\r\n ,\"microsoft.education/labs\": { \"SingularDisplayName\": \"Microsoft.Education lab\" }\r\n ,\"microsoft.education/labs/joinrequests\": { \"SingularDisplayName\": \"Microsoft.Education labs join request\" }\r\n ,\"microsoft.education/labs/students\": { \"SingularDisplayName\": \"Microsoft.Education labs student\" }\r\n ,\"microsoft.education/studentlabs\": { \"SingularDisplayName\": \"Microsoft.Education student lab\" }\r\n ,\"microsoft.elastic/monitors\": { \"SingularDisplayName\": \"Elastic Cloud Resource\" }\r\n ,\"microsoft.elasticsan/elasticsans\": { \"SingularDisplayName\": \"Elastic SAN\" }\r\n ,\"microsoft.energydataplatform/energyservices\": { \"SingularDisplayName\": \"Microsoft.EnergyDataPlatform energy service\" }\r\n ,\"microsoft.enterpriseknowledgegraph/services\": { \"SingularDisplayName\": \"Microsoft.EnterpriseKnowledgeGraph service\" }\r\n ,\"microsoft.enterprisesupport/enterprisesupports\": { \"SingularDisplayName\": \"Microsoft.EnterpriseSupport enterprise support\" }\r\n ,\"microsoft.eventgrid/domains\": { \"SingularDisplayName\": \"Event Grid Domain\" }\r\n ,\"microsoft.eventgrid/domains/topics\": { \"SingularDisplayName\": \"Event Grid Domain Topic\" }\r\n ,\"microsoft.eventgrid/eventsubscriptions\": { \"SingularDisplayName\": \"Microsoft.EventGrid event subscription\" }\r\n ,\"microsoft.eventgrid/extensiontopics\": { \"SingularDisplayName\": \"Event Grid extension topic\" }\r\n ,\"microsoft.eventgrid/namespaces\": { \"SingularDisplayName\": \"Event Grid Namespace\" }\r\n ,\"microsoft.eventgrid/namespaces/topics\": { \"SingularDisplayName\": \"Event Grid Namespace Topic\" }\r\n ,\"microsoft.eventgrid/namespaces/topics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Subscription\" }\r\n ,\"microsoft.eventgrid/namespaces/topicspaces\": { \"SingularDisplayName\": \"Event Grid Topic Space\" }\r\n ,\"microsoft.eventgrid/partnerconfigurations\": { \"SingularDisplayName\": \"Event Grid Partner Configuration\" }\r\n ,\"microsoft.eventgrid/partnerdestinations\": { \"SingularDisplayName\": \"Event Grid Partner Destination\" }\r\n ,\"microsoft.eventgrid/partnernamespaces\": { \"SingularDisplayName\": \"Event Grid Partner Namespace\" }\r\n ,\"microsoft.eventgrid/partnernamespaces/channels\": { \"SingularDisplayName\": \"Event Grid Channel\" }\r\n ,\"microsoft.eventgrid/partnerregistrations\": { \"SingularDisplayName\": \"Event Grid Partner Registration\" }\r\n ,\"microsoft.eventgrid/partnertopics\": { \"SingularDisplayName\": \"Event Grid Partner Topic\" }\r\n ,\"microsoft.eventgrid/systemtopics\": { \"SingularDisplayName\": \"Event Grid System Topic\" }\r\n ,\"microsoft.eventgrid/systemtopics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Grid Subscriptions\" }\r\n ,\"microsoft.eventgrid/topics\": { \"SingularDisplayName\": \"Event Grid Topic\" }\r\n ,\"microsoft.eventgrid/topictypes\": { \"SingularDisplayName\": \"Microsoft.EventGrid topic type\" }\r\n ,\"microsoft.eventgrid/verifiedpartners\": { \"SingularDisplayName\": \"Microsoft.EventGrid verified partner\" }\r\n ,\"microsoft.eventhub/clusters\": { \"SingularDisplayName\": \"Event Hubs Cluster\" }\r\n ,\"microsoft.eventhub/namespaces\": { \"SingularDisplayName\": \"Event Hubs namespace\" }\r\n ,\"microsoft.eventhub/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Event Hubs Geo-DR Alias\" }\r\n ,\"microsoft.eventhub/namespaces/eventhubs\": { \"SingularDisplayName\": \"Event Hubs Instance\" }\r\n ,\"microsoft.eventhub/namespaces/providers/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\r\n ,\"microsoft.eventhub/namespaces/schemagroups\": { \"SingularDisplayName\": \"Schema Group\" }\r\n ,\"microsoft.experimentation/experimentworkspaces\": { \"SingularDisplayName\": \"Experiment Workspace\" }\r\n ,\"microsoft.extendedlocation/customlocations\": { \"SingularDisplayName\": \"Custom location\" }\r\n ,\"microsoft.fabric/capacities\": { \"SingularDisplayName\": \"Fabric Capacity\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/operationresults\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric operation result\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private endpoint connection\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private link resource\" }\r\n ,\"microsoft.fairfieldgardens/deviceprovisioningstates\": { \"SingularDisplayName\": \"Microsoft.FairfieldGardens device provisioning state\" }\r\n ,\"microsoft.fairfieldgardens/provisioningresources\": { \"SingularDisplayName\": \"Fairfield Gardens\" }\r\n ,\"microsoft.fairfieldgardens/provisioningresources/provisioningpolicies\": { \"SingularDisplayName\": \"Provisioning policy\" }\r\n ,\"microsoft.falcon/namespaces\": { \"SingularDisplayName\": \"Microsoft.Falcon namespace\" }\r\n ,\"microsoft.features/featureprovidernamespaces/featureconfigurations\": { \"SingularDisplayName\": \"Preview features\" }\r\n ,\"microsoft.fidalgo/devcenters\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenter\" }\r\n ,\"microsoft.fidalgo/devcenters/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters attachednetwork\" }\r\n ,\"microsoft.fidalgo/devcenters/catalogs\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalog\" }\r\n ,\"microsoft.fidalgo/devcenters/catalogs/items\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalogs item\" }\r\n ,\"microsoft.fidalgo/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters devboxdefinition\" }\r\n ,\"microsoft.fidalgo/devcenters/environmenttypes\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters environment type\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters gallery\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries/images\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries image\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries/images/versions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries images version\" }\r\n ,\"microsoft.fidalgo/devcenters/mappings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters mapping\" }\r\n ,\"microsoft.fidalgo/machinedefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo machinedefinition\" }\r\n ,\"microsoft.fidalgo/networksettings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksetting\" }\r\n ,\"microsoft.fidalgo/networksettings/healthchecks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksettings healthcheck\" }\r\n ,\"microsoft.fidalgo/projects\": { \"SingularDisplayName\": \"Microsoft.Fidalgo project\" }\r\n ,\"microsoft.fidalgo/projects/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects attachednetwork\" }\r\n ,\"microsoft.fidalgo/projects/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects devboxdefinition\" }\r\n ,\"microsoft.fidalgo/projects/environments\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects environment\" }\r\n ,\"microsoft.fidalgo/projects/pools\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects pool\" }\r\n ,\"microsoft.fileshares/fileshares\": { \"SingularDisplayName\": \"File share\" }\r\n ,\"microsoft.fluidrelay/fluidrelayservers\": { \"SingularDisplayName\": \"Fluid Relay\" }\r\n ,\"microsoft.footprintmonitoring/profiles\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profile\" }\r\n ,\"microsoft.footprintmonitoring/profiles/experiments\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles experiment\" }\r\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoint\" }\r\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints/conditions\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoints condition\" }\r\n ,\"microsoft.gallery/myareas/galleryitems\": { \"SingularDisplayName\": \"Template\" }\r\n ,\"microsoft.genomics/accounts\": { \"SingularDisplayName\": \"Genomics account\" }\r\n ,\"microsoft.graph/azureadapplication\": { \"SingularDisplayName\": \"Entra application\" }\r\n ,\"microsoft.graph/azureadapplicationprototype\": { \"SingularDisplayName\": \"Microsoft.Graph Azure ad application prototype\" }\r\n ,\"microsoft.graphservices/accounts\": { \"SingularDisplayName\": \"Metered API account\" }\r\n ,\"microsoft.guestconfiguration/guestconfigurationassignments\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignment\" }\r\n ,\"microsoft.guestconfiguration/guestconfigurationassignments/reports\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignments report\" }\r\n ,\"microsoft.hanaonazure/hanainstances\": { \"SingularDisplayName\": \"SAP HANA on Azure\" }\r\n ,\"microsoft.hanaonazure/sapmonitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP Solutions (classic)\" }\r\n ,\"microsoft.hardware/orders\": { \"SingularDisplayName\": \"Microsoft.Hardware order\" }\r\n ,\"microsoft.hardwaresecuritymodules/cloudhsmclusters\": { \"SingularDisplayName\": \"Azure Cloud HSM\" }\r\n ,\"microsoft.hdinsight/clusterpools\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster pool\" }\r\n ,\"microsoft.hdinsight/clusterpools/clusters\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster\" }\r\n ,\"microsoft.hdinsight/clusterpools/clusters/instanceviews\": { \"SingularDisplayName\": \"Microsoft.HDInsight clusterpools clusters instance view\" }\r\n ,\"microsoft.hdinsight/clusters\": { \"SingularDisplayName\": \"HDInsight cluster\" }\r\n ,\"microsoft.healthbot/healthbots\": { \"SingularDisplayName\": \"Healthcare agent service\" }\r\n ,\"microsoft.healthcareapis/services\": { \"SingularDisplayName\": \"Azure API for FHIR\" }\r\n ,\"microsoft.healthcareapis/workspaces\": { \"SingularDisplayName\": \"Health Data Services workspace\" }\r\n ,\"microsoft.healthcareapis/workspaces/dicomservices\": { \"SingularDisplayName\": \"DICOM service\" }\r\n ,\"microsoft.healthcareapis/workspaces/fhirservices\": { \"SingularDisplayName\": \"FHIR service\" }\r\n ,\"microsoft.healthcareapis/workspaces/iotconnectors\": { \"SingularDisplayName\": \"MedTech service\" }\r\n ,\"microsoft.healthdataaiservices/deidservices\": { \"SingularDisplayName\": \"De-identification Service\" }\r\n ,\"microsoft.healthmodel/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\r\n ,\"microsoft.healthplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.HealthPlatform account\" }\r\n ,\"microsoft.help/diagnostics\": { \"SingularDisplayName\": \"Microsoft.Help diagnostic\" }\r\n ,\"microsoft.help/selfhelp\": { \"SingularDisplayName\": \"Microsoft.Help self help\" }\r\n ,\"microsoft.help/simplifiedsolutions\": { \"SingularDisplayName\": \"Microsoft.Help simplified solution\" }\r\n ,\"microsoft.help/solutions\": { \"SingularDisplayName\": \"Microsoft.Help solution\" }\r\n ,\"microsoft.help/troubleshooters\": { \"SingularDisplayName\": \"Microsoft.Help troubleshooter\" }\r\n ,\"microsoft.hpcworkbench/instances\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instance\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chamber\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/accessprofiles\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers access profile\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/filerequests\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file request\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/files\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/storages\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers storage\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/workloads\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers workload\" }\r\n ,\"microsoft.hpcworkbench/instances/consortiums\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances consortium\" }\r\n ,\"microsoft.hybridcloud/cloudconnections\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connection\" }\r\n ,\"microsoft.hybridcloud/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connector\" }\r\n ,\"microsoft.hybridcompute/arcgatewayassociatedresources\": { \"SingularDisplayName\": \"Arc gateway associated resource\" }\r\n ,\"microsoft.hybridcompute/arcserverwithwac\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/gateways\": { \"SingularDisplayName\": \"Arc gateway\" }\r\n ,\"microsoft.hybridcompute/licenses\": { \"SingularDisplayName\": \"Extended Security Updates - Windows Server 2012/R2\" }\r\n ,\"microsoft.hybridcompute/machines\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machines/microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\r\n ,\"microsoft.hybridcompute/machines/microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\r\n ,\"microsoft.hybridcompute/machines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.hybridcompute/machinesesu\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinespaygo\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinessoftwareassurance\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinessovereign\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Arc Private Link Scope\" }\r\n ,\"microsoft.hybridcompute/settings\": { \"SingularDisplayName\": \"Microsoft.HybridCompute setting\" }\r\n ,\"microsoft.hybridconnectivity/endpoints\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoint\" }\r\n ,\"microsoft.hybridconnectivity/endpoints/serviceconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoints service configuration\" }\r\n ,\"microsoft.hybridconnectivity/publiccloudconnectors\": { \"SingularDisplayName\": \"Multicloud connector\" }\r\n ,\"microsoft.hybridconnectivity/solutionconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configuration\" }\r\n ,\"microsoft.hybridconnectivity/solutionconfigurations/inventory\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configurations inventory\" }\r\n ,\"microsoft.hybridconnectivity/solutiontypes\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution type\" }\r\n ,\"microsoft.hybridcontainerservice/kubernetesversions\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService kubernetes version\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instance\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/agentpools\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances agent pool\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances hybrid identity metadata\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances upgrade profile\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusters\": { \"SingularDisplayName\": \"Kubernetes hybrid - Azure Arc\" }\r\n ,\"microsoft.hybridcontainerservice/skus\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService SKU\" }\r\n ,\"microsoft.hybridcontainerservice/storagespaces\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService storage space\" }\r\n ,\"microsoft.hybridcontainerservice/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService virtual network\" }\r\n ,\"microsoft.hybriddata/datamanagers\": { \"SingularDisplayName\": \"Microsoft.HybridData data manager\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data service\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definition\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions/jobs\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definitions job\" }\r\n ,\"microsoft.hybriddata/datamanagers/datastores\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store\" }\r\n ,\"microsoft.hybriddata/datamanagers/datastoretypes\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store type\" }\r\n ,\"microsoft.hybriddata/datamanagers/publickeys\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers public key\" }\r\n ,\"microsoft.hybridnetwork/configurationgroupvalues\": { \"SingularDisplayName\": \"Configuration Group Value\" }\r\n ,\"microsoft.hybridnetwork/devices\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Device\" }\r\n ,\"microsoft.hybridnetwork/networkfunctions\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Network Function\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publisher\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/artifactstores\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers artifact store\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers configuration group schema\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition group\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition groups network function definition version\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design group\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design groups network service design version\" }\r\n ,\"microsoft.hybridnetwork/publishers\": { \"SingularDisplayName\": \"Publisher\" }\r\n ,\"microsoft.hybridnetwork/publishers/artifactstores\": { \"SingularDisplayName\": \"Publisher Artifact Store\" }\r\n ,\"microsoft.hybridnetwork/publishers/artifactstores/artifactmanifests\": { \"SingularDisplayName\": \"Publisher Artifact Manifest\" }\r\n ,\"microsoft.hybridnetwork/publishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Configuration Group Schema\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Network Function Definition\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Network Function Definition Version\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Network Service Design\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Network Service Design Version\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management container\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rolloutsequences\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout sequence\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rollouttiers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout tier\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specification\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollout\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts/statuses\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollouts statuse\" }\r\n ,\"microsoft.hybridnetwork/sitenetworkservices\": { \"SingularDisplayName\": \"Site Network Service\" }\r\n ,\"microsoft.hybridnetwork/sites\": { \"SingularDisplayName\": \"Site\" }\r\n })[tolower(id)]\r\n}\r\n", - "$fxv#10": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Prices |=========================================================================================================\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='Transforms Prices_raw into FOCUS 1.2.', folder='Prices')\r\nPrices_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n let prices = materialize(\r\n Prices_raw\r\n | extend PricingCurrency = coalesce(Currency, CurrencyCode) // CurrencyCode last as a fallback only\r\n | extend x_SkuId = coalesce(SkuId, SkuID)\r\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\r\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\r\n | extend x_SkuTerm = isoMonths(Term)\r\n | project-rename\r\n SkuMeter = MeterName,\r\n x_BaseUnitPrice = BasePrice,\r\n x_EffectivePeriodEnd = EffectiveEndDate,\r\n x_EffectivePeriodStart = EffectiveStartDate,\r\n x_PricingUnitDescription = UnitOfMeasure,\r\n x_SkuIncludedQuantity = IncludedQuantity,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuMeterType = MeterType,\r\n x_SkuOfferId = OfferID,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPriceType = PriceType,\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTier = TierMinimumUnits\r\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, real(null)) // UnitPrice for savings plan is not the on-demand unit price\r\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, real(null)) // MarketPrice for savings plan is not the list price\r\n | extend ChargeCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Usage',\r\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\r\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\r\n ''\r\n )\r\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\r\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\r\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\r\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\r\n //\r\n // Get latest ingested row based on the unique ID\r\n | extend x_IngestionTime = ingestion_time()\r\n );\r\n //\r\n // Meters for reservations and savings plans to identify commitment eligibility\r\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\r\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\r\n //\r\n // Copy list/base/contracted prices from on-demand SKUs\r\n prices\r\n | where x_SkuPriceType == 'SavingsPlan'\r\n // If we use join, specify the shuffle key\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\r\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\r\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\r\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\r\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\r\n //\r\n // Set CommitmentDiscountCategory for reuse\r\n | extend CommitmentDiscountCategory = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Usage',\r\n x_SkuPriceType == 'SavingsPlan', 'Spend',\r\n ''\r\n )\r\n //\r\n // Calculate commitment discount eligibility\r\n // TODO: Would a join be faster?\r\n // TODO: Check this to ensure it's correct\r\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\r\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\r\n //\r\n // TODO: Implement x_CommitmentDiscountNormalizedRatio\r\n | extend x_CommitmentDiscountNormalizedRatio = real(null)\r\n //\r\n // Add PricingUnit and x_PricingBlockSize\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\r\n | lookup kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n //\r\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, real(null)) // Savings plan prices are for the effective price, not the contracted price\r\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\r\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\r\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\r\n | project\r\n BillingAccountId = tolower(case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n BillingAccountId startswith '/', BillingAccountId,\r\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\r\n )),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\r\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\r\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\r\n ''\r\n ),\r\n CommitmentDiscountUnit = case(\r\n isempty(CommitmentDiscountCategory), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), PricingUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', PricingUnit),\r\n ''\r\n ),\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed',\r\n ''\r\n ),\r\n PricingCurrency,\r\n PricingUnit,\r\n SkuId = coalesce(ProductId, ProductID),\r\n SkuMeter,\r\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n strlen(x_BillingAccountId) > 32, 'MCA',\r\n strlen(x_BillingAccountId) < 32, 'EA',\r\n 'Unknown'\r\n ),\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\r\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory = case(\r\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_SkuDescription = Product,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\r\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\r\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\r\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\r\n}\r\n\r\n// Prices_final_v1_2 table\r\n.create-merge table Prices_final_v1_2 (\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n ChargeCategory: string,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountType: string,\r\n CommitmentDiscountUnit: string,\r\n ContractedUnitPrice: real,\r\n ListUnitPrice: real,\r\n PricingCategory: string,\r\n PricingCurrency: string, // Azure\r\n PricingUnit: string,\r\n SkuId: string,\r\n SkuMeter: string, // Azure\r\n SkuPriceId: string,\r\n SkuPriceIdv2: string, // Hubs add-on\r\n x_BaseUnitPrice: real, // Azure\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure MCA\r\n x_BillingProfileId: string, // Azure MCA\r\n x_CommitmentDiscountNormalizedRatio: real, // Hubs add-on\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_ContractedUnitPriceDiscount: real, // Hubs add-on\r\n x_ContractedUnitPriceDiscountPercent: real, // Hubs add-on\r\n x_EffectivePeriodEnd: datetime, // Azure\r\n x_EffectivePeriodStart: datetime, // Azure\r\n x_EffectiveUnitPrice: real, // Azure\r\n x_EffectiveUnitPriceDiscount: real, // Hubs add-on\r\n x_EffectiveUnitPriceDiscountPercent: real, // Hubs add-on\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_PricingBlockSize: real, // Hubs add-on\r\n x_PricingSubcategory: string, // Hubs add-on\r\n x_PricingUnitDescription: string, // Azure\r\n x_SkuDescription: string, // Azure\r\n x_SkuId: string, // Azure\r\n x_SkuIncludedQuantity: real, // Azure EA\r\n x_SkuMeterCategory: string, // Azure\r\n x_SkuMeterId: string, // Azure\r\n x_SkuMeterSubcategory: string, // Azure\r\n x_SkuMeterType: string, // Azure\r\n x_SkuPriceType: string, // Azure\r\n x_SkuProductId: string, // Azure\r\n x_SkuRegion: string, // Azure\r\n x_SkuServiceFamily: string, // Azure\r\n x_SkuOfferId: string, // Azure EA\r\n x_SkuPartNumber: string, // Azure EA\r\n x_SkuTerm: int, // Azure\r\n x_SkuTier: real, // Azure MCA\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_TotalUnitPriceDiscount: real, // Hubs add-on\r\n x_TotalUnitPriceDiscountPercent: real // Hubs add-on\r\n)\r\n\r\n// Update policy for Prices_raw -> Prices_final_v1_2\r\n.alter table Prices_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Prices_raw\",\r\n \"Query\": \"Prices_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Cost and usage |=================================================================================================\r\n// Supported versions:\r\n// - MS: 1.2-preview, 1.0, 1.0-preview(v1)\r\n// https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0\r\n// https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024\r\n// https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 \r\n// https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All costs transformed to FOCUS 1.2.', folder='Costs')\r\nCosts_transform_v1_2()\r\n{\r\n let checkString = (column: string, oldValue: string, newValue: string) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n let checkInt = (column: string, oldValue: int, newValue: int) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n let checkReal = (column: string, oldValue: real, newValue: real) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n Costs_raw\r\n //\r\n // Dedupe rows\r\n | extend x_IngestionTime = ingestion_time()\r\n | extend x_ChargeId = ''\r\n // TODO: Consider adding a unique charge ID per row\r\n // hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // // 1. Resource hierarchy (including resource name), highest to lowest\r\n // BillingAccountId,\r\n // x_InvoiceSectionId,\r\n // x_AccountOwnerId,\r\n // SubAccountId,\r\n // x_ResourceGroupName,\r\n // ResourceName,\r\n // // 2. Resource details\r\n // ResourceId,\r\n // RegionId,\r\n // Tags,\r\n // CommitmentDiscountId,\r\n // x_CostCenter,\r\n // // 4. Meter details\r\n // SkuPriceId,\r\n // x_SkuMeterId,\r\n // x_SkuPartNumber,\r\n // x_SkuOfferId,\r\n // x_SkuDetails,\r\n // // 5. Date\r\n // ChargePeriodStart\r\n // ))\r\n //\r\n // Identify data quality issues\r\n // TODO: Remove x_SourceChanges in v1_3 (or later)\r\n | extend x_SourceChanges = trim_end(',', strcat(\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\r\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\r\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\r\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\r\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\r\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\r\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\r\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\r\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\r\n 'XEffectiveUnitPriceRoundingError,', ''),\r\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\r\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\r\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\r\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\r\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\r\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\r\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\r\n ))\r\n //\r\n // Handle provider columns that moved to FOCUS\r\n | extend PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency)\r\n //\r\n // Backup original prices/costs before the merge\r\n | extend old_ContractedCost = ContractedCost\r\n | extend old_ContractedUnitPrice = ContractedUnitPrice\r\n | extend old_ListCost = ListCost\r\n | extend old_ListUnitPrice = ListUnitPrice\r\n | extend old_x_EffectiveUnitPrice = x_EffectiveUnitPrice\r\n //\r\n // Fix columns needed in other changes\r\n | extend old_ProviderName = ProviderName, ProviderName = case(\r\n isnotempty(ProviderName), ProviderName,\r\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\r\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\r\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\r\n ''\r\n )\r\n //\r\n // Identify source\r\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\r\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\r\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\r\n ''\r\n ))\r\n // Append version check error code\r\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\r\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\r\n )\r\n //\r\n // Fix quantities\r\n | extend old_PricingQuantity = PricingQuantity, PricingQuantity = case(\r\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\r\n PricingQuantity\r\n )\r\n | extend old_ConsumedQuantity = ConsumedQuantity, ConsumedQuantity = case(\r\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\r\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\r\n ConsumedQuantity\r\n )\r\n //\r\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\r\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\r\n and (isempty(ListUnitPrice) or isempty(ContractedUnitPrice) or ListUnitPrice == 0 or ContractedUnitPrice == 0)\r\n and x_EffectiveUnitPrice != 0\r\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\r\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\r\n | as allCosts\r\n | where tmp_MissingPrices\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | as costsWithMissingPrices\r\n | join kind=leftouter (\r\n Prices_final_v1_2\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\r\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\r\n ) on tmp_ReservationPriceLookupKey\r\n //\r\n // Select the best price to use for each row\r\n | extend x_EffectiveUnitPrice = case(\r\n // If price is a rounding error away from the billed price, use the billed price\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\r\n // If price is a rounding error away from the contracted price, use the contracted price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ContractedUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\r\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ListUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // Otherwise, assume the contracted price is the same as list price to support aggregations\r\n ContractedUnitPrice\r\n )\r\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\r\n | extend ContractedCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\r\n // ContractedCost is 0 in all other scenarios...\r\n // If 0 and there's a billed cost and prices are the same, use BilledCost\r\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\r\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\r\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume EffectiveCost\r\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ContractedCost\r\n )\r\n | extend ListCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\r\n // ListCost is 0 in all other scenarios...\r\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\r\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume ContractedCost\r\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ListCost\r\n )\r\n // Merge the rest of the unmodified cost records and remove excess columns\r\n | union (allCosts | where not(tmp_MissingPrices))\r\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\r\n //\r\n | extend SkuPriceDetails = parse_json(SkuPriceDetails)\r\n | extend Tags = parse_json(Tags)\r\n | extend x_SkuDetails = parse_json(x_SkuDetails)\r\n //\r\n // Handle FOCUS 1.0-preview\r\n | extend old_ChargeSubcategory = ChargeSubcategory\r\n | extend old_ChargeCategory = ChargeCategory, ChargeCategory = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Credit', 'Credit',\r\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\r\n ChargeCategory\r\n )\r\n | extend old_ChargeClass = ChargeClass, ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass)\r\n //\r\n // Populate CapacityReservationId when not specified\r\n | extend CapacityReservationId = coalesce(CapacityReservationId, tostring(coalesce(x_SkuDetails.VMCapacityReservationId, SkuPriceDetails.VMCapacityReservationId, SkuPriceDetails.x_VMCapacityReservationId)))\r\n | extend old_CapacityReservationStatus = CapacityReservationStatus, CapacityReservationStatus = case(\r\n isempty(CapacityReservationId), '',\r\n isnotempty(CapacityReservationStatus), CapacityReservationStatus,\r\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\r\n 'Used'\r\n )\r\n //\r\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\r\n | extend old_ChargeFrequency = ChargeFrequency, ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency)\r\n //\r\n // Commitment discounts\r\n | extend x_CommitmentDiscountNormalizedRatio = case(\r\n // Calculate from CommitmentDiscountQuantity, if specified\r\n isnotempty(CommitmentDiscountQuantity) and CommitmentDiscountQuantity != 0, CommitmentDiscountQuantity / PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\r\n // Not applicable\r\n isempty(CommitmentDiscountStatus), real(null),\r\n // Parse from SKU details if not specified explicitly\r\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, SkuPriceDetails.RINormalizationRatio, SkuPriceDetails.x_RINormalizationRatio, dynamic(1)))\r\n )\r\n | extend old_CommitmentDiscountQuantity = CommitmentDiscountQuantity, CommitmentDiscountQuantity = case(\r\n // FOCUS 1.2\r\n isnotempty(CommitmentDiscountQuantity), CommitmentDiscountQuantity,\r\n // FOCUS 1.0-preview, 1.0\r\n isempty(CommitmentDiscountStatus), real(null),\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\r\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\r\n real(null)\r\n )\r\n | extend old_CommitmentDiscountUnit = CommitmentDiscountUnit, CommitmentDiscountUnit = case(\r\n // FOCUS 1.2\r\n isnotempty(CommitmentDiscountUnit), CommitmentDiscountUnit,\r\n // FOCUS 1.0\r\n isempty(CommitmentDiscountQuantity), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\r\n ''\r\n )\r\n | extend old_CommitmentDiscountStatus = CommitmentDiscountStatus, CommitmentDiscountStatus = case(\r\n // FOCUS 1.0+\r\n isnotempty(CommitmentDiscountStatus), CommitmentDiscountStatus,\r\n // FOCUS 1.0-preview\r\n ChargeSubcategory == 'Used Commitment', 'Used',\r\n ChargeSubcategory == 'Unused Commitment', 'Unused',\r\n ''\r\n )\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n //\r\n // Pricing\r\n | extend old_x_AmortizationClass = x_AmortizationClass, x_AmortizationClass = case(\r\n // FOCUS 1.2\r\n isnotempty(x_AmortizationClass), x_AmortizationClass,\r\n // FOCUS 1.0-preview+\r\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\r\n ''\r\n )\r\n | extend old_PricingCategory = PricingCategory, PricingCategory = case(\r\n // FOCUS 1.0+\r\n isnotempty(PricingCategory), PricingCategory,\r\n // FOCUS 1.0-preview\r\n PricingCategory == 'On-Demand', 'Standard',\r\n PricingCategory == 'Commitment-Based', 'Committed',\r\n ''\r\n )\r\n //\r\n // Commitment discount utilization\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n //\r\n // BUG: Fix ContractedCost that has bad values\r\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\r\n //\r\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\r\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), real(null))\r\n | extend old_ConsumedUnit = ConsumedUnit, ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\r\n //\r\n // Convert IDs to lowercase for consistency\r\n | extend BillingAccountId = tolower(BillingAccountId)\r\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\r\n //\r\n // BUG: Remove EffectiveCost for commitment discount purchases\r\n | extend old_EffectiveCost = EffectiveCost, EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), EffectiveCost)\r\n | extend old_x_EffectiveCostInUsd = x_EffectiveCostInUsd, x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), x_EffectiveCostInUsd)\r\n //\r\n // Clean up resource columns\r\n | extend old_ResourceId = ResourceId, ResourceId = case(\r\n isnotempty(ResourceId), ResourceId,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\r\n ResourceId\r\n )\r\n | extend old_ResourceName = ResourceName, ResourceName = tolower(case(\r\n isnotempty(ResourceName), ResourceName,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\r\n ResourceName\r\n ))\r\n | extend old_x_ResourceType = x_ResourceType, x_ResourceType = case(\r\n isnotempty(x_ResourceType), x_ResourceType,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\r\n x_ResourceType\r\n )\r\n | extend old_ResourceType = ResourceType, ResourceType = case(\r\n // Use existing resource type display name unless it's an internal resource type ID\r\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\r\n // Use CommitmentDiscountType for commitment discount purchases\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\r\n // Look up display name from internal type\r\n isnotempty(x_ResourceType), coalesce(tostring(resource_type(x_ResourceType).SingularDisplayName), ResourceType, x_ResourceType),\r\n ResourceType\r\n )\r\n //\r\n // Handle missing values\r\n | extend old_PublisherName = PublisherName, PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, '')\r\n //\r\n // Handle FOCUS 1.0-preview Region column\r\n | extend old_Region = Region\r\n | extend old_RegionId = RegionId, RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region))\r\n | extend RegionName = coalesce(RegionName, Region)\r\n //\r\n // SKU properties\r\n | extend x_SkuCoreCount = toint(coalesce(SkuPriceDetails.CoreCount, SkuPriceDetails.x_VCPUs, x_SkuDetails.VCPUs, SkuPriceDetails.x_VCores, x_SkuDetails.VCores, SkuPriceDetails.x_vCores, x_SkuDetails.vCores))\r\n | extend x_SkuInstanceType = tostring(coalesce(SkuPriceDetails.InstanceType, SkuPriceDetails.x_ServiceType, x_SkuDetails.ServiceType, SkuPriceDetails.x_ServerSku, x_SkuDetails.ServerSku))\r\n | extend x_SkuOperatingSystem = case(\r\n isnotempty(SkuPriceDetails.OperatingSystem), SkuPriceDetails.OperatingSystem,\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Canonical', 'Linux',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType)\r\n )\r\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\r\n | extend SkuPriceDetails = case(\r\n // FOCUS 1.2\r\n isnotempty(SkuPriceDetails), SkuPriceDetails,\r\n // FOCUS 1.0-preview, 1.0\r\n parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\r\n // Prefix all keys with x_ first to avoid double-prefixing\r\n , @'([\\{,])\"', @'\\1\"x_')\r\n // CoreCount for number of CPUs/vCPUs/cores/vCores\r\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\r\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\r\n // TODO: DiskSpace for disk size in GiB\r\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\r\n // TODO: GpuCount for the number of GPUs\r\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\r\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\r\n // TODO: InstanceSeries for the size family/series\r\n // TODO: MemorySize for the RAM in GiB\r\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\r\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\r\n // OperatingSystem for the OS name\r\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\r\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\r\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\r\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\r\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\r\n )\r\n )\r\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\r\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\r\n SkuPriceDetails)\r\n //\r\n // Azure Hybrid Benefit\r\n | extend tmp_SqlAhb = tolower(coalesce(x_SkuDetails.AHB, SkuPriceDetails.x_AHB))\r\n | extend x_SkuLicenseType = case(\r\n ChargeCategory != 'Usage', '',\r\n x_SkuMeterCategory in ('Virtual Machines', 'Virtual Machine Licenses') and (x_SkuMeterSubcategory contains 'Windows' or coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL'), 'Windows Server',\r\n isnotempty(tmp_SqlAhb) or x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\r\n ''\r\n )\r\n | extend x_SkuLicenseStatus = case(\r\n isempty(x_SkuLicenseType), '',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL' or tmp_SqlAhb == 'true' or x_SkuMeterSubcategory contains 'Azure Hybrid Benefit', 'Enabled',\r\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not Enabled',\r\n ''\r\n )\r\n | extend x_SkuLicenseQuantity = case(\r\n isempty(x_SkuCoreCount) or isempty(x_SkuLicenseType), int(null),\r\n x_SkuCoreCount <= 8, int(8),\r\n x_SkuCoreCount > 8, x_SkuCoreCount,\r\n int(null)\r\n )\r\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\r\n //\r\n // Savings\r\n | extend x_CommitmentDiscountSavings = iff(isempty(ContractedCost) or ContractedCost == 0 or ContractedCost - EffectiveCost < 0.0001, real(0), ContractedCost - EffectiveCost)\r\n | extend x_NegotiatedDiscountSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - ContractedCost < 0.0001, real(0), ListCost - ContractedCost)\r\n | extend x_TotalSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - EffectiveCost < 0.0001, real(0), ListCost - EffectiveCost)\r\n | extend x_CommitmentDiscountPercent = iff(isempty(ContractedUnitPrice) or ContractedUnitPrice == 0 or ContractedUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\r\n | extend x_NegotiatedDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - ContractedUnitPrice < 0.0001, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\r\n | extend x_TotalDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\r\n //\r\n // Minor fixes\r\n | extend old_BillingPeriodEnd = BillingPeriodEnd, BillingPeriodEnd = startofmonth(BillingPeriodEnd)\r\n | extend old_BillingPeriodStart = BillingPeriodStart, BillingPeriodStart = startofmonth(BillingPeriodStart)\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n CapacityReservationId,\r\n CapacityReservationStatus,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\r\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\r\n EffectiveCost,\r\n InvoiceId = coalesce(InvoiceId, x_InvoiceId),\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory, // TODO: Populate ServiceSubcategory from ServiceName when missing\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceDetails,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName = iff(isempty(SubAccountId), '', SubAccountName),\r\n SubAccountType,\r\n Tags,\r\n x_AccountId = iff(x_AccountId == '-2', '', x_AccountId),\r\n x_AccountName = iff(x_AccountId == '-2', '', x_AccountName),\r\n x_AccountOwnerId = iff(x_AccountId == '-2', '', x_AccountOwnerId),\r\n x_AmortizationClass,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\r\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\r\n ProviderName\r\n ),\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingItemCode,\r\n x_BillingItemName,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountPercent,\r\n x_CommitmentDiscountSavings,\r\n x_CommitmentDiscountSpendEligibility = '', // TODO: Add x_CommitmentDiscountSpendEligibility for Costs\r\n x_CommitmentDiscountUsageEligibility = '', // TODO: Add x_CommitmentDiscountUsageEligibility for Costs\r\n x_CommitmentDiscountUtilizationAmount,\r\n x_CommitmentDiscountUtilizationPotential,\r\n x_CommodityCode,\r\n x_CommodityName,\r\n x_ComponentName,\r\n x_ComponentType,\r\n x_ConsumedCoreHours,\r\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd),\r\n x_CostAllocationRuleName,\r\n x_CostCategories = parse_json(x_CostCategories),\r\n x_CostCenter,\r\n x_CostType,\r\n x_Credits = parse_json(x_Credits),\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount = parse_json(x_Discount),\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InstanceID,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId = case(\r\n x_InvoiceSectionId == '-2', '',\r\n x_InvoiceSectionId\r\n ),\r\n x_InvoiceSectionName = case(\r\n x_InvoiceSectionName == 'Unassigned', '',\r\n x_InvoiceSectionName\r\n ),\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_NegotiatedDiscountPercent,\r\n x_NegotiatedDiscountSavings,\r\n x_Operation,\r\n x_OwnerAccountID,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription = iff(x_PricingUnitDescription == 'Unassigned', '', x_PricingUnitDescription),\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName = tolower(x_ResourceGroupName),\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServiceModel, // TODO: Populate from ServiceName when missing\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuCoreCount,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuInstanceType,\r\n x_SkuIsCreditEligible,\r\n x_SkuLicenseQuantity,\r\n x_SkuLicenseStatus,\r\n x_SkuLicenseType,\r\n x_SkuLicenseUnit,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOperatingSystem,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuPlanName,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceValues = bag_merge(\r\n checkString('BillingPeriodEnd', old_BillingPeriodEnd, BillingPeriodEnd),\r\n checkString('BillingPeriodStart', old_BillingPeriodStart, BillingPeriodStart),\r\n checkString('CapacityReservationStatus', old_CapacityReservationStatus, CapacityReservationStatus),\r\n checkString('ChargeCategory', old_ChargeCategory, ChargeCategory),\r\n checkString('ChargeClass', old_ChargeClass, ChargeClass),\r\n checkString('ChargeSubcategory', old_ChargeSubcategory, ''), // Not included in final schema; use empty string\r\n checkString('ChargeFrequency', old_ChargeFrequency, ChargeFrequency),\r\n checkReal('CommitmentDiscountQuantity', old_CommitmentDiscountQuantity, CommitmentDiscountQuantity),\r\n checkString('CommitmentDiscountUnit', old_CommitmentDiscountUnit, CommitmentDiscountUnit),\r\n checkString('CommitmentDiscountStatus', old_CommitmentDiscountStatus, CommitmentDiscountStatus),\r\n checkReal('ConsumedQuantity', old_ConsumedQuantity, ConsumedQuantity),\r\n checkString('ConsumedUnit', old_ConsumedUnit, ConsumedUnit),\r\n checkReal('ContractedCost', old_ContractedCost, ContractedCost),\r\n checkReal('ContractedUnitPrice', old_ContractedUnitPrice, ContractedUnitPrice),\r\n checkReal('EffectiveCost', old_EffectiveCost, EffectiveCost),\r\n checkReal('ListCost', old_ListCost, ListCost),\r\n checkReal('ListUnitPrice', old_ListUnitPrice, ListUnitPrice),\r\n checkString('PricingCategory', old_PricingCategory, PricingCategory),\r\n checkReal('PricingQuantity', old_PricingQuantity, PricingQuantity),\r\n checkString('ProviderName', old_ProviderName, ProviderName),\r\n checkString('PublisherName', old_PublisherName, PublisherName),\r\n checkString('Region', old_Region, ''), // Not included in final schema; use empty string\r\n checkString('RegionId', old_RegionId, RegionId),\r\n checkString('ResourceId', old_ResourceId, ResourceId),\r\n checkString('ResourceName', old_ResourceName, ResourceName),\r\n checkString('ResourceType', old_ResourceType, ResourceType),\r\n checkString('x_AmortizationClass', old_x_AmortizationClass, x_AmortizationClass),\r\n checkReal('x_EffectiveCostInUsd', old_x_EffectiveCostInUsd, x_EffectiveCostInUsd),\r\n checkReal('x_EffectiveUnitPrice', old_x_EffectiveUnitPrice, x_EffectiveUnitPrice),\r\n checkString('x_ResourceType', old_x_ResourceType, x_ResourceType)\r\n ),\r\n x_SourceVersion,\r\n x_SubproductName,\r\n x_TotalDiscountPercent,\r\n x_TotalSavings,\r\n x_UsageType\r\n}\r\n\r\n// Costs_final_v1_2 table\r\n.create-merge table Costs_final_v1_2 (\r\n AvailabilityZone: string,\r\n BilledCost: real,\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingAccountType: string,\r\n BillingCurrency: string,\r\n BillingPeriodEnd: datetime,\r\n BillingPeriodStart: datetime,\r\n CapacityReservationId: string,\r\n CapacityReservationStatus: string,\r\n ChargeCategory: string,\r\n ChargeClass: string,\r\n ChargeDescription: string,\r\n ChargeFrequency: string,\r\n ChargePeriodEnd: datetime,\r\n ChargePeriodStart: datetime,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountId: string,\r\n CommitmentDiscountName: string,\r\n CommitmentDiscountQuantity: real,\r\n CommitmentDiscountStatus: string,\r\n CommitmentDiscountType: string,\r\n CommitmentDiscountUnit: string,\r\n ConsumedQuantity: real,\r\n ConsumedUnit: string,\r\n ContractedCost: real,\r\n ContractedUnitPrice: real,\r\n EffectiveCost: real,\r\n InvoiceId: string,\r\n InvoiceIssuerName: string,\r\n ListCost: real,\r\n ListUnitPrice: real,\r\n PricingCategory: string,\r\n PricingCurrency: string,\r\n PricingQuantity: real,\r\n PricingUnit: string,\r\n ProviderName: string,\r\n PublisherName: string,\r\n RegionId: string,\r\n RegionName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n ServiceCategory: string,\r\n ServiceName: string,\r\n ServiceSubcategory: string,\r\n SkuId: string,\r\n SkuMeter: string,\r\n SkuPriceDetails: dynamic,\r\n SkuPriceId: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n SubAccountType: string,\r\n Tags: dynamic,\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_AmortizationClass: string, // Azure 1.2-preview+\r\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingItemCode: string, // Alibaba 1.0\r\n x_BillingItemName: string, // Alibaba 1.0\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_CommitmentDiscountNormalizedRatio: real, // Azure 1.2-preview+\r\n x_CommitmentDiscountPercent: real, // Hubs add-on\r\n x_CommitmentDiscountSavings: real, // Hubs add-on\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUtilizationAmount: real, // Hubs add-on\r\n x_CommitmentDiscountUtilizationPotential: real, // Hubs add-on\r\n x_CommodityCode: string, // Alibaba 1.0\r\n x_CommodityName: string, // Alibaba 1.0\r\n x_ComponentName: string, // Tencent 1.0\r\n x_ComponentType: string, // Tencent 1.0\r\n x_ConsumedCoreHours: real, // Hubs add-on\r\n x_ContractedCostInUsd: real, // Azure 1.0+\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_CostType: string, // GCP Jan 2024\r\n x_Credits: dynamic, // GCP Jan 2024\r\n x_CurrencyConversionRate: real, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: dynamic, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_InstanceID: string, // Alibaba 1.0\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_NegotiatedDiscountPercent:real, // Hubs add-on\r\n x_NegotiatedDiscountSavings:real, // Hubs add-on\r\n x_Operation: string, // AWS 1.0\r\n x_OwnerAccountID: string, // Tencent 1.0\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServiceModel: string, // Azure 1.2-preview+\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuCoreCount: int, // Hubs add-on\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\r\n x_SkuInstanceType: string, // Hubs add-on\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuLicenseQuantity: int, // Hubs add-on\r\n x_SkuLicenseStatus: string, // Hubs add-on\r\n x_SkuLicenseType: string, // Hubs add-on\r\n x_SkuLicenseUnit: string, // Hubs add-on\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOperatingSystem: string, // Hubs add-on\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuPlanName: string, // Azure 1.2-preview+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceChanges: string, // Hubs add-on\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceValues: dynamic, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubproductName: string, // Tencent 1.0\r\n x_TotalDiscountPercent: real, // Hubs add-on\r\n x_TotalSavings: real, // Hubs add-on\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Update policy for Costs_raw -> Costs_final_v1_2 table\r\n.alter table Costs_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Costs_raw\",\r\n \"Query\": \"Costs_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Actual costs |===================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\r\nActualCosts_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n ActualCosts_raw\r\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodEnd = Date + 1d,\r\n ChargePeriodStart = Date,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId = '',\r\n SkuMeter = MeterName,\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentType = '',\r\n x_ComponentName = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = '',\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel,\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for ActualCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"ActualCosts_raw\",\r\n \"Query\": \"ActualCosts_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Amortized costs |================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\r\nAmortizedCosts_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n AmortizedCosts_raw\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodEnd = Date + 1d,\r\n ChargePeriodStart = Date,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId = '',\r\n SkuMeter = MeterName,\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentType = '',\r\n x_ComponentName = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = '',\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel,\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for AmortizedCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"AmortizedCosts_raw\",\r\n \"Query\": \"AmortizedCosts_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All commitment discount usage transformed to FOCUS 1.2. This includes reservationdeatils_raw.', folder='Commitment discounts')\r\nCommitmentDiscountUsage_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n CommitmentDiscountUsage_raw\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Handle resource columns\r\n | extend ResourceId = tolower(InstanceId)\r\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\r\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\r\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\r\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\r\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\r\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n ChargePeriodEnd = UsageDate + 1d,\r\n ChargePeriodStart = UsageDate,\r\n CommitmentDiscountCategory = 'Usage',\r\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\r\n CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\r\n CommitmentDiscountType = 'Reservation',\r\n CommitmentDiscountUnit = case(\r\n InstanceFlexibilityRatio == 1, 'Hours',\r\n InstanceFlexibilityRatio != 1, 'Normalized Hours',\r\n ''\r\n ),\r\n ConsumedQuantity = UsedHours,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\r\n x_CommitmentDiscountCommittedAmount = ReservedHours,\r\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\r\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\r\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\r\n x_IngestionTime = ingestion_time(),\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n // x_RowId = hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // CommitmentDiscountId,\r\n // ResourceId,\r\n // ChargePeriodStart\r\n // )),\r\n x_ServiceModel,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\r\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\r\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\r\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\r\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\r\n}\r\n\r\n// CommitmentDiscountUsage_final_v1_2 table\r\n.create-merge table CommitmentDiscountUsage_final_v1_2 (\r\n ChargePeriodEnd: datetime, // Hubs add-on\r\n ChargePeriodStart: datetime, // MS 2023-03-01\r\n CommitmentDiscountCategory: string, // Hubs add-on\r\n CommitmentDiscountId: string, // MS 2023-03-01\r\n CommitmentDiscountQuantity: real, // MS 2023-03-01\r\n CommitmentDiscountType: string, // Hubs add-on\r\n CommitmentDiscountUnit: string, // Hubs add-on\r\n ConsumedQuantity: real, // MS 2023-03-01\r\n ProviderName: string, // Hubs add-on\r\n ResourceId: string, // MS 2023-03-01\r\n ResourceName: string, // Hubs add-on\r\n ResourceType: string, // Hubs add-on\r\n ServiceCategory: string, // Hubs add-on\r\n ServiceName: string, // Hubs add-on\r\n ServiceSubcategory: string, // Hubs add-on\r\n SubAccountId: string, // Hubs add-on\r\n x_CommitmentDiscountCommittedCount: real, // MS 2023-03-01\r\n x_CommitmentDiscountCommittedAmount: real, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedRatio: real, // MS 2023-03-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_ResourceGroupName: string, // Hubs add-on\r\n x_ResourceType: string, // Hubs add-on\r\n x_ServiceModel: string, // Hubs add-on\r\n x_SkuOrderId: string, // MS 2023-03-01\r\n x_SkuSize: string, // MS 2023-03-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string // Hubs add-on\r\n)\r\n\r\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_2 table\r\n.alter table CommitmentDiscountUsage_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"CommitmentDiscountUsage_raw\",\r\n \"Query\": \"CommitmentDiscountUsage_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All recommendations transformed to FOCUS 1.2.', folder='Recommendations')\r\nRecommendations_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Recommendations_raw\r\n | extend x_IngestionTime = ingestion_time()\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Convert JSON cost columns to real\r\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\r\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\r\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\r\n //\r\n // Build recommendation details\r\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\r\n | extend x_RecommendationDetails = case(\r\n // Use incoming x_RecommendationDetails first\r\n isnotempty(x_RecommendationDetails), x_RecommendationDetails,\r\n // Create one for reservation recommendations if needed\r\n x_SourceType == 'ReservationRecommendations', bag_pack(\r\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\r\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\r\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\r\n 'CommitmentDiscountResourceType', ResourceType,\r\n 'CommitmentDiscountScope', Scope,\r\n 'LookbackPeriodDuration', case(\r\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\r\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\r\n ''\r\n ),\r\n 'LookbackPeriodStart', FirstUsageDate,\r\n 'RecommendedQuantity', RecommendedQuantity,\r\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\r\n 'RegionId', Location,\r\n 'RegionName', RegionName,\r\n 'SkuMeterId', MeterId,\r\n 'SkuPriceDetails', SkuProperties,\r\n 'SkuSize', coalesce(SKU, SkuName),\r\n 'SkuTerm', isoMonths(Term)\r\n ),\r\n dynamic({})\r\n )\r\n //\r\n // Prefer specified date, then fall back to generating a date based on reservation recommendation lookback period, then validate to ensure it's not in the future\r\n | extend x_RecommendationDate = coalesce(x_RecommendationDate, FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d))\r\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\r\n //\r\n | project\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n SubAccountId = coalesce(SubAccountId, iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), '')),\r\n SubAccountName,\r\n x_EffectiveCostAfter = coalesce(x_EffectiveCostAfter, TotalCostWithReservedInstances),\r\n x_EffectiveCostBefore = coalesce(x_EffectiveCostBefore, CostWithNoReservedInstances),\r\n x_EffectiveCostSavings = coalesce(x_EffectiveCostSavings, NetSavings),\r\n x_IngestionTime,\r\n x_RecommendationCategory, // TODO: Set for reservation recommendations\r\n x_RecommendationDate,\r\n x_RecommendationDescription,\r\n x_RecommendationDetails,\r\n x_RecommendationId, // TODO: Set for reservation recommendations\r\n x_ResourceGroupName,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n// Recommendations_final_v1_2 table\r\n.create-merge table Recommendations_final_v1_2 (\r\n ProviderName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n x_EffectiveCostAfter: real,\r\n x_EffectiveCostBefore: real,\r\n x_EffectiveCostSavings: real,\r\n x_IngestionTime: datetime,\r\n x_RecommendationCategory: string,\r\n x_RecommendationDate: datetime,\r\n x_RecommendationDescription: string,\r\n x_RecommendationDetails: dynamic,\r\n x_RecommendationId: string,\r\n x_ResourceGroupName: string,\r\n x_SourceName: string,\r\n x_SourceProvider: string,\r\n x_SourceType: string,\r\n x_SourceVersion: string\r\n)\r\n\r\n// Update policy for Recommendations_raw -> Recommendations_final_v1_2 table\r\n.alter table Recommendations_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Recommendations_raw\",\r\n \"Query\": \"Recommendations_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All transactions transformed to FOCUS 1.2.', folder='Transactions')\r\nTransactions_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Transactions_raw\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Handle BillingPeriodStart/End\r\n | extend BillingMonth = tostring(BillingMonth)\r\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\r\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n BilledCost = Amount,\r\n BillingAccountId = case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\r\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\r\n ''\r\n ),\r\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\r\n BillingCurrency = Currency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory = case(\r\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = case(\r\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\r\n EventType == 'Refund', 'Correction',\r\n ''\r\n ),\r\n ChargeDescription = Description,\r\n ChargeFrequency = case(\r\n BillingFrequency == 'OneTime', 'One-Time',\r\n BillingFrequency == 'Recurring', 'Recurring',\r\n BillingFrequency\r\n ),\r\n ChargePeriodStart = EventDate,\r\n InvoiceId,\r\n PricingQuantity = Quantity,\r\n PricingUnit = 'Reservations',\r\n ProviderName,\r\n RegionId = Region,\r\n RegionName = Region,\r\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\r\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerEmail,\r\n x_CostCenter = CostCenter,\r\n x_InvoiceNumber = Invoice,\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\r\n x_IngestionTime = ingestion_time(),\r\n x_MonetaryCommitment = MonetaryCommitment,\r\n x_Overage = Overage,\r\n x_PurchasingBillingAccountId = PurchasingEnrollment,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuOrderName = ReservationOrderName,\r\n x_SkuSize = ArmSkuName,\r\n x_SkuTerm = isoMonths(Term),\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId = PurchasingSubscriptionGuid,\r\n x_TransactionType = EventType\r\n}\r\n\r\n// Transactions_final_v1_2 table\r\n.create-merge table Transactions_final_v1_2 (\r\n BilledCost: real, // MS CM EA+MCA 2023-05-01\r\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\r\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\r\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n ChargeCategory: string, // Hubs add-on\r\n ChargeClass: string, // Hubs add-on\r\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\r\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\r\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n InvoiceId: string, // MS CM MCA 2023-05-01\r\n PricingQuantity: real, // MS CM EA+MCA 2023-05-01\r\n PricingUnit: string, // Hubs add-on\r\n ProviderName: string, // Hubs add-on\r\n RegionId: string, // MS CM EA+MCA 2023-05-01\r\n RegionName: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\r\n x_AccountName: string, // MS CM EA 2023-05-01\r\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\r\n x_CostCenter: string, // MS CM EA 2023-05-01\r\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_MonetaryCommitment: real, // MS CM EA 2023-05-01\r\n x_Overage: real, // MS CM EA 2023-05-01\r\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\r\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\r\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\r\n)\r\n\r\n// Update policy for Transactions_raw -> Transactions_final_v1_2 table\r\n.alter table Transactions_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Transactions_raw\",\r\n \"Query\": \"Transactions_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n", - "$fxv#11": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Common utility functions\r\n//\r\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\r\n//======================================================================================================================\r\n\r\n\r\n//===| Date functions |=================================================================================================\r\n\r\n// monthstring\r\n.create-or-alter function \r\nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \r\nmonthstring(['date']: datetime, length: int = 9)\r\n{\r\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\r\n}\r\n\r\n// datestring\r\n.create-or-alter function \r\nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \r\ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n let month = (d: datetime) { monthstring(d, 3) };\r\n let endDate = iff(end == datetime('0001-01-01'), start, end);\r\n let sameDate = startofday(start) == startofday(endDate);\r\n let sameMonth = startofmonth(start) == startofmonth(endDate);\r\n let sameYear = startofyear(start) == startofyear(endDate);\r\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\r\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\r\n let currentYear = sameYear and startofyear(start) == startofyear(now());\r\n case(\r\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\r\n fullYear,\r\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\r\n // 1 full mo, same year | Mmm yyyy\r\n fullMonth and sameMonth and sameYear,\r\n strcat(month(start), ' ', getyear(start)),\r\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\r\n fullMonth and sameYear,\r\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\r\n fullMonth and not(sameYear),\r\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\r\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\r\n sameDate,\r\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\r\n not(fullMonth) and sameMonth and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\r\n not(fullMonth) and not(sameMonth) and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\r\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\r\n )\r\n}\r\n\r\n// daterange\r\n.create-or-alter function \r\nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \r\ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n datestring(start, end)\r\n}\r\n\r\n// monthsago\r\n.create-or-alter function \r\nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\r\nmonthsago(months: int)\r\n{\r\n datetime_add('month', -months, startofmonth(now()))\r\n}\r\n\r\n\r\n//===| Number functions |===============================================================================================\r\n// NOTE: Must be defined before string converters\r\n\r\n// delta\r\n.create-or-alter function \r\nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \r\ndelta(oldval: double, newval: double)\r\n{\r\n (newval - todouble(oldval))/oldval\r\n}\r\n\r\n// percentOfTotal\r\n// NOTE: Must be before percent() function\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercentOfTotal(t: (Count: long), tot: long)\r\n{\r\n let total = todouble(tot);\r\n t \r\n | extend Percent = round(Count / total * 100, 3) \r\n | order by Count desc\r\n}\r\n\r\n// percent\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercent(t: (Count: long))\r\n{\r\n let total = todouble(toscalar(t | summarize sum(Count)));\r\n percentOfTotal(t, total)\r\n}\r\n\r\n// plusminus\r\n.create-or-alter function \r\nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\r\nplusminus(val: string)\r\n{\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, val, strcat('+', val))\r\n}\r\n\r\n// updown\r\n.create-or-alter function \r\nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\r\nupdown(val: string)\r\n{\r\n // TODO: Handle 0\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\r\n}\r\n\r\n\r\n//===| String functions |===============================================================================================\r\n\r\n// percentstring\r\n// NOTE: Must be defined before deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\r\npercentstring(num: double, total: double = 1.0, places: int = 9)\r\n{\r\n let value = 1.0 * num / total * 100;\r\n strcat(case(\r\n places != 9, round(value, places),\r\n value < 10, round(value, 2),\r\n round(value, 1)\r\n ), '%')\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// arraystring\r\n.create-or-alter function \r\nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\r\narraystring(arr: dynamic)\r\n{\r\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\r\n tostring(arr)\r\n , @'^\\[\"', '')\r\n , @'\"\\]$', '')\r\n , @'^, ', '')\r\n , @', $', '')\r\n , @'^\\[]$', '')\r\n , '\",\"', ', ')\r\n}\r\n\r\n// deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\r\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\r\n{\r\n let d = delta(oldval, newval);\r\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\r\n}\r\n\r\n// diffstring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\r\ndiffstring(oldval: double, newval: double, places: int = 1)\r\n{\r\n plusminus(round(newval - oldval, places))\r\n}\r\n\r\n// numberstring\r\n.create-or-alter function \r\nwith (docstring = 'Convert a number to a string', folder = 'Common')\r\nnumberstring(num: double, abbrev: bool = true)\r\n{\r\n replace_regex(case(\r\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\r\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\r\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\r\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\r\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\r\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\r\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\r\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\r\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\r\n tostring(num)\r\n ), @'\\.0$', '')\r\n}\r\n\r\n\r\n//===| Other |==========================================================================================================\r\n\r\n// ifempty\r\n.create-or-alter function \r\nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\r\nifempty(val: dynamic, defaultVal: dynamic)\r\n{\r\n iff(isempty(val), defaultVal, val)\r\n}\r\n", - "$fxv#12": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / Open data functions\r\n// Wrap Ingestion database tables for easy access.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// PricingUnits\r\n.create-or-alter function\r\nwith (docstring = 'Gets pricing units from the FinOps toolkit PricingUnits open data.', folder = 'OpenData')\r\nPricingUnits()\r\n{\r\n database('Ingestion').PricingUnits\r\n}\r\n\r\n// Regions\r\n.create-or-alter function\r\nwith (docstring = 'Gets regions from the FinOps toolkit Regions open data.', folder = 'OpenData')\r\nRegion()\r\n{\r\n database('Ingestion').Regions\r\n}\r\n\r\n// ResourceTypes\r\n.create-or-alter function\r\nwith (docstring = 'Gets resource types from the FinOps toolkit ResourceTypes open data.', folder = 'OpenData')\r\nResourceType()\r\n{\r\n database('Ingestion').ResourceTypes\r\n}\r\n\r\n// Services\r\n.create-or-alter function\r\nwith (docstring = 'Gets services from the FinOps toolkit Services open data.', folder = 'OpenData')\r\nServices()\r\n{\r\n database('Ingestion').Services\r\n}\r\n", - "$fxv#13": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / FOCUS 1.0 functions\r\n// Used for reporting with backward compatibility.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// CommitmentDiscountUsage_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.0.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage_v1_0()\r\n{\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\r\n | union (\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n x_CommitmentDiscountCommittedCount = todecimal(x_CommitmentDiscountCommittedCount),\r\n x_CommitmentDiscountCommittedAmount = todecimal(x_CommitmentDiscountCommittedAmount),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio)\r\n )\r\n | project\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount,\r\n x_CommitmentDiscountCommittedAmount,\r\n x_CommitmentDiscountNormalizedGroup,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountQuantity,\r\n x_IngestionTime,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceModel,\r\n x_SkuOrderId,\r\n x_SkuSize,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Costs_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.0.', folder = 'Costs')\r\nCosts_v1_0()\r\n{\r\n database('Ingestion').Costs_final_v1_0\r\n | union (\r\n database('Ingestion').Costs_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n ContractedCost = todecimal(ContractedCost),\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n EffectiveCost = todecimal(EffectiveCost),\r\n ListCost = todecimal(ListCost),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\r\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\r\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\r\n // Rename columns\r\n | project-rename\r\n x_InvoiceId = InvoiceId,\r\n x_PricingCurrency = PricingCurrency,\r\n x_SkuMeterName = SkuMeter\r\n // Generate historical x_SkuDetails format from SkuPriceDetails\r\n | extend x_SkuDetails = iff(isnotempty(x_SkuDetails), x_SkuDetails, parse_json(replace_regex(tostring(SkuPriceDetails), @'([\\{,])\"x_', @'\\1\"')))\r\n )\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost,\r\n ContractedUnitPrice,\r\n EffectiveCost,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SkuId,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType,\r\n Tags,\r\n x_AccountId,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_ContractedCostInUsd,\r\n x_CostAllocationRuleName,\r\n x_CostCategories,\r\n x_CostCenter,\r\n x_Credits,\r\n x_CostType,\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount,\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InvoiceId,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_Operation,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingCurrency,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuIsCreditEligible,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_UsageType\r\n}\r\n\r\n\r\n// Prices_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices aligned to FOCUS 1.0.', folder = 'Prices')\r\nPrices_v1_0()\r\n{\r\n database('Ingestion').Prices_final_v1_0\r\n | union (\r\n database('Ingestion').Prices_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n x_BaseUnitPrice = todecimal(x_BaseUnitPrice),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\r\n x_ContractedUnitPriceDiscount = todecimal(x_ContractedUnitPriceDiscount),\r\n x_ContractedUnitPriceDiscountPercent = todecimal(x_ContractedUnitPriceDiscountPercent),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_EffectiveUnitPriceDiscount = todecimal(x_EffectiveUnitPriceDiscount),\r\n x_EffectiveUnitPriceDiscountPercent = todecimal(x_EffectiveUnitPriceDiscountPercent),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize),\r\n x_SkuIncludedQuantity = todecimal(x_SkuIncludedQuantity),\r\n x_SkuTier = todecimal(x_SkuTier),\r\n x_TotalUnitPriceDiscount = todecimal(x_TotalUnitPriceDiscount),\r\n x_TotalUnitPriceDiscountPercent = todecimal(x_TotalUnitPriceDiscountPercent) \r\n // Rename columns\r\n | project-rename\r\n x_PricingCurrency = PricingCurrency,\r\n x_SkuMeterName = SkuMeter\r\n )\r\n | project\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType,\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingUnit,\r\n SkuId,\r\n SkuPriceId,\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent,\r\n x_EffectivePeriodEnd,\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingCurrency,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_SkuDescription,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent\r\n}\r\n\r\n\r\n// Recommendations_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.0.', folder = 'Recommendations')\r\nRecommendations_v1_0()\r\n{\r\n database('Ingestion').Recommendations_final_v1_0\r\n | union (\r\n database('Ingestion').Recommendations_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n x_EffectiveCostAfter = todecimal(x_EffectiveCostAfter),\r\n x_EffectiveCostBefore = todecimal(x_EffectiveCostBefore),\r\n x_EffectiveCostSavings = todecimal(x_EffectiveCostSavings)\r\n )\r\n | project\r\n ProviderName,\r\n SubAccountId,\r\n x_IngestionTime,\r\n x_EffectiveCostAfter,\r\n x_EffectiveCostBefore,\r\n x_EffectiveCostSavings,\r\n x_RecommendationDate,\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Transactions_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.0.', folder = 'Transactions')\r\nTransactions_v1_0()\r\n{\r\n database('Ingestion').Transactions_final_v1_0\r\n | union (\r\n database('Ingestion').Transactions_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n x_MonetaryCommitment = todecimal(x_MonetaryCommitment),\r\n x_Overage = todecimal(x_Overage)\r\n // Rename columns\r\n | project-rename\r\n x_InvoiceId = InvoiceId\r\n )\r\n | project\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodStart,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n RegionId,\r\n RegionName,\r\n SubAccountId,\r\n SubAccountName,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_CostCenter,\r\n x_InvoiceId,\r\n x_InvoiceNumber,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_IngestionTime,\r\n x_MonetaryCommitment,\r\n x_Overage,\r\n x_PurchasingBillingAccountId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuSize,\r\n x_SkuTerm,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId,\r\n x_TransactionType\r\n}\r\n", - "$fxv#14": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / FOCUS 1.2 functions\r\n// Used for reporting with backward compatibility.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// CommitmentDiscountUsage_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.2.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage_v1_2()\r\n{\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\r\n | union (\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n ConsumedQuantity = toreal(ConsumedQuantity),\r\n x_CommitmentDiscountCommittedCount = toreal(x_CommitmentDiscountCommittedCount),\r\n x_CommitmentDiscountCommittedAmount = toreal(x_CommitmentDiscountCommittedAmount),\r\n x_CommitmentDiscountNormalizedRatio = toreal(x_CommitmentDiscountNormalizedRatio)\r\n // Add new columns\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceSubcategory) on x_ResourceType\r\n | extend CommitmentDiscountQuantity = ConsumedQuantity * x_CommitmentDiscountNormalizedRatio\r\n | extend CommitmentDiscountUnit = case(\r\n x_CommitmentDiscountNormalizedRatio == 1, 'Hours',\r\n x_CommitmentDiscountNormalizedRatio > 1, 'Normalized Hours',\r\n ''\r\n )\r\n )\r\n | project\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount,\r\n x_CommitmentDiscountCommittedAmount,\r\n x_CommitmentDiscountNormalizedGroup,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_IngestionTime,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceModel,\r\n x_SkuOrderId,\r\n x_SkuSize,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Costs_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.2.', folder = 'Costs')\r\nCosts_v1_2()\r\n{\r\n database('Ingestion').Costs_final_v1_2\r\n | union (\r\n database('Ingestion').Costs_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n BilledCost = toreal(BilledCost),\r\n ConsumedQuantity = toreal(ConsumedQuantity),\r\n ContractedCost = toreal(ContractedCost),\r\n ContractedUnitPrice = toreal(ContractedUnitPrice),\r\n EffectiveCost = toreal(EffectiveCost),\r\n ListCost = toreal(ListCost),\r\n ListUnitPrice = toreal(ListUnitPrice),\r\n PricingQuantity = toreal(PricingQuantity),\r\n x_BilledCostInUsd = toreal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = toreal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = toreal(x_BillingExchangeRate),\r\n x_ContractedCostInUsd = toreal(x_ContractedCostInUsd),\r\n x_CurrencyConversionRate = toreal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = toreal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = toreal(x_ListCostInUsd),\r\n x_PricingBlockSize = toreal(x_PricingBlockSize)\r\n // Rename columns\r\n | project-rename\r\n InvoiceId = x_InvoiceId,\r\n PricingCurrency = x_PricingCurrency,\r\n SkuMeter = x_SkuMeterName\r\n // Add new columns\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | extend CapacityReservationId = tostring(x_SkuDetails.VMCapacityReservationId)\r\n | extend CapacityReservationStatus = case(\r\n isempty(CapacityReservationId), '',\r\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\r\n 'Used'\r\n )\r\n | extend x_CommitmentDiscountNormalizedRatio = case(\r\n // Not applicable\r\n isempty(CommitmentDiscountStatus), real(null),\r\n // Parse from SKU details if not specified explicitly\r\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, dynamic(1)))\r\n )\r\n | extend CommitmentDiscountQuantity = case(\r\n isempty(CommitmentDiscountStatus), real(null),\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\r\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\r\n real(null)\r\n )\r\n | extend CommitmentDiscountUnit = case(\r\n isempty(CommitmentDiscountQuantity), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\r\n ''\r\n )\r\n | extend x_AmortizationClass = case(\r\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\r\n ''\r\n )\r\n // Hubs add-ons\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n | extend x_SkuCoreCount = toint(coalesce(x_SkuDetails.VCPUs, x_SkuDetails.VCores, x_SkuDetails.vCores))\r\n | extend x_SkuInstanceType = tostring(coalesce(x_SkuDetails.ServiceType, x_SkuDetails.ServerSku))\r\n | extend x_SkuOperatingSystem = case(\r\n x_SkuDetails.ImageType == 'Canonical', 'Linux',\r\n x_SkuDetails.ImageType == 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\r\n x_SkuDetails.ImageType\r\n )\r\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\r\n | extend tmp_SqlAhb = tolower(x_SkuDetails.AHB)\r\n | extend x_SkuLicenseType = case(\r\n x_SkuDetails.ImageType contains 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\r\n ''\r\n )\r\n | extend x_SkuLicenseStatus = case(\r\n isnotempty(x_SkuLicenseType) or tmp_SqlAhb == 'true' or (x_SkuMeterSubcategory contains 'Azure Hybrid Benefit'), 'Enabled',\r\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not enabled',\r\n ''\r\n )\r\n | extend x_SkuLicenseQuantity = case(\r\n isempty(x_SkuCoreCount), int(null),\r\n x_SkuCoreCount <= 8, int(8),\r\n x_SkuCoreCount > 8, x_SkuCoreCount,\r\n int(null)\r\n )\r\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\r\n | extend x_CommitmentDiscountSavings = iff(ContractedCost < EffectiveCost, real(0), ContractedCost - EffectiveCost)\r\n | extend x_NegotiatedDiscountSavings = iff(ListCost < ContractedCost, real(0), ListCost - ContractedCost)\r\n | extend x_TotalSavings = iff(ListCost < EffectiveCost, real(0), ListCost - EffectiveCost)\r\n | extend x_CommitmentDiscountPercent = iff(ContractedUnitPrice == 0, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\r\n | extend x_NegotiatedDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\r\n | extend x_TotalDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\r\n // SkuPriceDetails conversion -- Must be after hubs add-ons\r\n | extend SkuPriceDetails = parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\r\n // Prefix all keys with x_ first to avoid double-prefixing\r\n , @'([\\{,])\"', @'\\1\"x_')\r\n // CoreCount for number of CPUs/vCPUs/cores/vCores\r\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\r\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\r\n // TODO: DiskSpace for disk size in GiB\r\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\r\n // TODO: GpuCount for the number of GPUs\r\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\r\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\r\n // TODO: InstanceSeries for the size family/series\r\n // TODO: MemorySize for the RAM in GiB\r\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\r\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\r\n // OperatingSystem for the OS name\r\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\r\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\r\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\r\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\r\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\r\n )\r\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\r\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\r\n SkuPriceDetails)\r\n )\r\n | extend SkuPriceDetails = iff(isnotempty(SkuPriceDetails), SkuPriceDetails, parse_json(replace_regex(tostring(x_SkuDetails), @'([\\{,])\"', @'\\1\"x_')))\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n CapacityReservationId,\r\n CapacityReservationStatus,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost,\r\n ContractedUnitPrice,\r\n EffectiveCost,\r\n InvoiceId,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceDetails,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType,\r\n Tags,\r\n x_AccountId,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_AmortizationClass,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingItemCode,\r\n x_BillingItemName,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountPercent,\r\n x_CommitmentDiscountSavings,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_CommitmentDiscountUtilizationAmount,\r\n x_CommitmentDiscountUtilizationPotential,\r\n x_CommodityCode,\r\n x_CommodityName,\r\n x_ComponentName,\r\n x_ComponentType,\r\n x_ConsumedCoreHours,\r\n x_ContractedCostInUsd,\r\n x_CostAllocationRuleName,\r\n x_CostCategories,\r\n x_CostCenter,\r\n x_CostType,\r\n x_Credits,\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount,\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InstanceID,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_NegotiatedDiscountPercent,\r\n x_NegotiatedDiscountSavings,\r\n x_Operation,\r\n x_OwnerAccountID,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServiceModel,\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuCoreCount,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuInstanceType,\r\n x_SkuIsCreditEligible,\r\n x_SkuLicenseQuantity,\r\n x_SkuLicenseStatus,\r\n x_SkuLicenseType,\r\n x_SkuLicenseUnit,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOperatingSystem,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuPlanName,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceValues,\r\n x_SourceVersion,\r\n x_SubproductName,\r\n x_TotalDiscountPercent,\r\n x_TotalSavings,\r\n x_UsageType\r\n}\r\n\r\n\r\n// Prices_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices aligned to FOCUS 1.2.', folder = 'Prices')\r\nPrices_v1_2()\r\n{\r\n database('Ingestion').Prices_final_v1_2\r\n | union (\r\n database('Ingestion').Prices_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n ContractedUnitPrice = toreal(ContractedUnitPrice),\r\n ListUnitPrice = toreal(ListUnitPrice),\r\n x_BaseUnitPrice = toreal(x_BaseUnitPrice),\r\n x_ContractedUnitPriceDiscount = toreal(x_ContractedUnitPriceDiscount),\r\n x_ContractedUnitPriceDiscountPercent = toreal(x_ContractedUnitPriceDiscountPercent),\r\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\r\n x_EffectiveUnitPriceDiscount = toreal(x_EffectiveUnitPriceDiscount),\r\n x_EffectiveUnitPriceDiscountPercent = toreal(x_EffectiveUnitPriceDiscountPercent),\r\n x_PricingBlockSize = toreal(x_PricingBlockSize),\r\n x_SkuIncludedQuantity = toreal(x_SkuIncludedQuantity),\r\n x_SkuTier = toreal(x_SkuTier),\r\n x_TotalUnitPriceDiscount = toreal(x_TotalUnitPriceDiscount),\r\n x_TotalUnitPriceDiscountPercent = toreal(x_TotalUnitPriceDiscountPercent) \r\n // Rename columns\r\n | project-rename\r\n PricingCurrency = x_PricingCurrency,\r\n SkuMeter = x_SkuMeterName\r\n )\r\n | project\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingUnit,\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceId,\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent,\r\n x_EffectivePeriodEnd,\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_SkuDescription,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent\r\n}\r\n\r\n\r\n// Recommendations_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.2.', folder = 'Recommendations')\r\nRecommendations_v1_2()\r\n{\r\n database('Ingestion').Recommendations_final_v1_2\r\n | union (\r\n database('Ingestion').Recommendations_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n x_EffectiveCostAfter = toreal(x_EffectiveCostAfter),\r\n x_EffectiveCostBefore = toreal(x_EffectiveCostBefore),\r\n x_EffectiveCostSavings = toreal(x_EffectiveCostSavings)\r\n )\r\n | project\r\n ProviderName,\r\n SubAccountId,\r\n x_IngestionTime,\r\n x_EffectiveCostAfter,\r\n x_EffectiveCostBefore,\r\n x_EffectiveCostSavings,\r\n x_RecommendationDate,\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Transactions_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.2.', folder = 'Transactions')\r\nTransactions_v1_2()\r\n{\r\n database('Ingestion').Transactions_final_v1_2\r\n | union (\r\n database('Ingestion').Transactions_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n BilledCost = toreal(BilledCost),\r\n PricingQuantity = toreal(PricingQuantity),\r\n x_MonetaryCommitment = toreal(x_MonetaryCommitment),\r\n x_Overage = toreal(x_Overage)\r\n // Rename columns\r\n | project-rename\r\n InvoiceId = x_InvoiceId\r\n )\r\n | project\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodStart,\r\n InvoiceId,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n RegionId,\r\n RegionName,\r\n SubAccountId,\r\n SubAccountName,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_CostCenter,\r\n x_InvoiceNumber,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_IngestionTime,\r\n x_MonetaryCommitment,\r\n x_Overage,\r\n x_PurchasingBillingAccountId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuSize,\r\n x_SkuTerm,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId,\r\n x_TransactionType\r\n}\r\n\r\n\r\n//======================================================================================================================\r\n// Latest FOCUS version\r\n//======================================================================================================================\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage()\r\n{\r\n CommitmentDiscountUsage_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\r\nCosts()\r\n{\r\n Costs_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\r\nPrices()\r\n{\r\n Prices_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\r\nRecommendations()\r\n{\r\n Recommendations_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\r\nTransactions()\r\n{\r\n Transactions_v1_2()\r\n}\r\n", - "$fxv#15": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / Latest FOCUS version functions\r\n// Used for ad hoc queries.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage()\r\n{\r\n CommitmentDiscountUsage_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\r\nCosts()\r\n{\r\n Costs_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\r\nPrices()\r\n{\r\n Prices_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\r\nRecommendations()\r\n{\r\n Recommendations_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\r\nTransactions()\r\n{\r\n Transactions_v1_2()\r\n}\r\n", - "$fxv#2": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_3(id: string) {\r\n dynamic({\r\n \"microsoft.hybridnetwork/vendors\": { \"SingularDisplayName\": \"Azure Network Function Manager ? vendor\" }\r\n ,\"microsoft.hybridonboarding/extensionmanagers\": { \"SingularDisplayName\": \"Microsoft.HybridOnboarding extension manager\" }\r\n ,\"microsoft.impact/connectors\": { \"SingularDisplayName\": \"Impact Reporting Connector\" }\r\n ,\"microsoft.impact/impactcategories\": { \"SingularDisplayName\": \"Microsoft.Impact impact category\" }\r\n ,\"microsoft.impact/topologyimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact topology impact\" }\r\n ,\"microsoft.impact/workloadimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact workload impact\" }\r\n ,\"microsoft.impact/workloadimpacts/insights\": { \"SingularDisplayName\": \"Microsoft.Impact workload impacts insight\" }\r\n ,\"microsoft.importexport/jobs\": { \"SingularDisplayName\": \"Microsoft.ImportExport job\" }\r\n ,\"microsoft.insights/actiongroups\": { \"SingularDisplayName\": \"Action group\" }\r\n ,\"microsoft.insights/activitylogalerts\": { \"SingularDisplayName\": \"Activity log alert rule\" }\r\n ,\"microsoft.insights/alertrules\": { \"SingularDisplayName\": \"Microsoft.Insights alertrule\" }\r\n ,\"microsoft.insights/alertrules/incidents\": { \"SingularDisplayName\": \"Microsoft.insights alertrules incident\" }\r\n ,\"microsoft.insights/autoscalesettings\": { \"SingularDisplayName\": \"Microsoft.Insights autoscalesetting\" }\r\n ,\"microsoft.insights/components\": { \"SingularDisplayName\": \"Application Insights app\" }\r\n ,\"microsoft.insights/datacollectionendpoints\": { \"SingularDisplayName\": \"Data collection endpoint\" }\r\n ,\"microsoft.insights/datacollectionruleassociations\": { \"SingularDisplayName\": \"Microsoft.Insights data collection rule association\" }\r\n ,\"microsoft.insights/datacollectionrules\": { \"SingularDisplayName\": \"Data collection rule\" }\r\n ,\"microsoft.insights/datacollectionrulesresources\": { \"SingularDisplayName\": \"Data collection rule associated resource\" }\r\n ,\"microsoft.insights/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\r\n ,\"microsoft.insights/diagnosticsettingscategories\": { \"SingularDisplayName\": \"Microsoft.Insights diagnostic settings category\" }\r\n ,\"microsoft.insights/guestdiagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic setting\" }\r\n ,\"microsoft.insights/guestdiagnosticsettingsassociation\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic settings association\" }\r\n ,\"microsoft.insights/logprofiles\": { \"SingularDisplayName\": \"Microsoft.Insights logprofile\" }\r\n ,\"microsoft.insights/metricalerts\": { \"SingularDisplayName\": \"Metric alert rule\" }\r\n ,\"microsoft.insights/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights notification statu\" }\r\n ,\"microsoft.insights/privatelinkscopeoperationstatuses\": { \"SingularDisplayName\": \"Microsoft.insights private link scope operation statuse\" }\r\n ,\"microsoft.insights/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Monitor Private Link Scope\" }\r\n ,\"microsoft.insights/scheduledqueryrules\": { \"SingularDisplayName\": \"Log search alert rule\" }\r\n ,\"microsoft.insights/tenantactiongroups\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action group\" }\r\n ,\"microsoft.insights/tenantactiongroups/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action groups notification statu\" }\r\n ,\"microsoft.insights/vminsightsonboardingstatuses\": { \"SingularDisplayName\": \"Microsoft.Insights VM insights onboarding statuse\" }\r\n ,\"microsoft.insights/webtests\": { \"SingularDisplayName\": \"Application Insights availability test\" }\r\n ,\"microsoft.insights/workbooks\": { \"SingularDisplayName\": \"Azure Workbook\" }\r\n ,\"microsoft.insights/workbooktemplates\": { \"SingularDisplayName\": \"Azure Workbook Template\" }\r\n ,\"microsoft.integrationspaces/spaces\": { \"SingularDisplayName\": \"Integration Environment\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twin\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/assets\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins asset\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/executionplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins execution plan\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/testplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test plan\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/tests\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test\" }\r\n ,\"microsoft.inventory/subscriptioninternalproperties\": { \"SingularDisplayName\": \"Microsoft.Inventory subscription internal property\" }\r\n ,\"microsoft.iotcentral/iotapps\": { \"SingularDisplayName\": \"IoT Central Application\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces\": { \"SingularDisplayName\": \"Firmware analysis workspace\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmware\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares/summaries\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmwares summary\" }\r\n ,\"microsoft.iotoperations/instances\": { \"SingularDisplayName\": \"Azure IoT Operations\" }\r\n ,\"microsoft.iotoperations/instances/brokers\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances broker\" }\r\n ,\"microsoft.iotoperations/instances/brokers/authentications\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authentication\" }\r\n ,\"microsoft.iotoperations/instances/brokers/authorizations\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authorization\" }\r\n ,\"microsoft.iotoperations/instances/brokers/listeners\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers listener\" }\r\n ,\"microsoft.iotoperations/instances/dataflowendpoints\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow endpoint\" }\r\n ,\"microsoft.iotoperations/instances/dataflowprofiles\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profile\" }\r\n ,\"microsoft.iotoperations/instances/dataflowprofiles/dataflows\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profiles dataflow\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instance\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances dataset\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances pipeline\" }\r\n ,\"microsoft.iotoperationsmq/mq\": { \"SingularDisplayName\": \"IoT Operations Ops MQ\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/authentication\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authentication\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/authorization\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authorization\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/listener\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker listener\" }\r\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector topic map\" }\r\n ,\"microsoft.iotoperationsmq/mq/diagnosticservice\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq diagnostic service\" }\r\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector topic map\" }\r\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector topic map\" }\r\n ,\"microsoft.iotoperationsorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator instance\" }\r\n ,\"microsoft.iotoperationsorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator solution\" }\r\n ,\"microsoft.iotoperationsorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator target\" }\r\n ,\"microsoft.iotsecurity/alerttypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity alert type\" }\r\n ,\"microsoft.iotsecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity defender setting\" }\r\n ,\"microsoft.iotsecurity/onpremisesensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity on premise sensor\" }\r\n ,\"microsoft.iotsecurity/recommendationtypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity recommendation type\" }\r\n ,\"microsoft.iotsecurity/sensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity sensor\" }\r\n ,\"microsoft.iotsecurity/sites\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity site\" }\r\n ,\"microsoft.keyvault/managedhsms\": { \"SingularDisplayName\": \"Azure Key Vault Managed HSM\" }\r\n ,\"microsoft.keyvault/vaults\": { \"SingularDisplayName\": \"Key vault\" }\r\n ,\"microsoft.kubernetes/connectedclusters\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc extension\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc namespace\" }\r\n ,\"microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\r\n ,\"microsoft.kubernetesconfiguration/extensiontypes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension type\" }\r\n ,\"microsoft.kubernetesconfiguration/extensiontypes/versions\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension types version\" }\r\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configuration\" }\r\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations/operations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configurations operation\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scope\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private endpoint connection\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private link resource\" }\r\n ,\"microsoft.kubernetesconfiguration/sourcecontrolconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration source control configuration\" }\r\n ,\"microsoft.kubernetesruntime/bgppeers\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime bgp peer\" }\r\n ,\"microsoft.kubernetesruntime/loadbalancers\": { \"SingularDisplayName\": \"Arc Load Balancer\" }\r\n ,\"microsoft.kubernetesruntime/services\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime service\" }\r\n ,\"microsoft.kubernetesruntime/storageclasses\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime storage class\" }\r\n ,\"microsoft.kusto/clusters\": { \"SingularDisplayName\": \"Azure Data Explorer Cluster\" }\r\n ,\"microsoft.kusto/clusters/databases\": { \"SingularDisplayName\": \"Azure Data Explorer Database\" }\r\n ,\"microsoft.labservices/labaccounts\": { \"SingularDisplayName\": \"Lab account\" }\r\n ,\"microsoft.labservices/labaccounts/labs\": { \"SingularDisplayName\": \"Lab\" }\r\n ,\"microsoft.labservices/labplans\": { \"SingularDisplayName\": \"Lab plan\" }\r\n ,\"microsoft.labservices/labs\": { \"SingularDisplayName\": \"Lab\" }\r\n ,\"microsoft.liftrpilot/organizations\": { \"SingularDisplayName\": \"Azure Pilot\" }\r\n ,\"microsoft.loadtestservice/loadtestmappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test mapping\" }\r\n ,\"microsoft.loadtestservice/loadtestprofilemappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test profile mapping\" }\r\n ,\"microsoft.loadtestservice/loadtests\": { \"SingularDisplayName\": \"Azure Load Testing\" }\r\n ,\"microsoft.loadtestservice/playwrightworkspaces\": { \"SingularDisplayName\": \"Playwright Workspace\" }\r\n ,\"microsoft.logic/businessprocesses\": { \"SingularDisplayName\": \"Business Process\" }\r\n ,\"microsoft.logic/integrationaccounts\": { \"SingularDisplayName\": \"Logic app integration account\" }\r\n ,\"microsoft.logic/integrationserviceenvironments\": { \"SingularDisplayName\": \"Integration Service Environment\" }\r\n ,\"microsoft.logic/integrationserviceenvironments/health\": { \"SingularDisplayName\": \"Microsoft.Logic integration service environments health\" }\r\n ,\"microsoft.logic/integrationserviceenvironments/managedapis\": { \"SingularDisplayName\": \"Managed Connector\" }\r\n ,\"microsoft.logic/templates\": { \"SingularDisplayName\": \"Logic Apps Template\" }\r\n ,\"microsoft.logic/workflows\": { \"SingularDisplayName\": \"Logic app\" }\r\n ,\"microsoft.logz/monitors\": { \"SingularDisplayName\": \"Logz.io\" }\r\n ,\"microsoft.logz/monitors/accounts\": { \"SingularDisplayName\": \"Logz sub account\" }\r\n ,\"microsoft.m365/m365resources\": { \"SingularDisplayName\": \"Microsoft.M365 m365 resource\" }\r\n ,\"microsoft.m365consumptionservices/services\": { \"SingularDisplayName\": \"Microsoft.M365ConsumptionServices service\" }\r\n ,\"microsoft.machinelearning/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plan\" }\r\n ,\"microsoft.machinelearning/commitmentplans/commitmentassociations\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plans commitment association\" }\r\n ,\"microsoft.machinelearning/webservices\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) web service\" }\r\n ,\"microsoft.machinelearning/workspaces\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) workspace\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation account\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspace\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspaces project\" }\r\n ,\"microsoft.machinelearningservices/aistudio\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.machinelearningservices/aistudiocreate\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.machinelearningservices/registries\": { \"SingularDisplayName\": \"Azure Machine Learning registry\" }\r\n ,\"microsoft.machinelearningservices/workspaces\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\r\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints\": { \"SingularDisplayName\": \"Machine learning online endpoint\" }\r\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints/deployments\": { \"SingularDisplayName\": \"Machine learning online deployment\" }\r\n ,\"microsoft.machinelearningservices/workspacescreate\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\r\n ,\"microsoft.maintenance/configurationassignments\": { \"SingularDisplayName\": \"Microsoft.Maintenance configuration assignment\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurations\": { \"SingularDisplayName\": \"Maintenance Configuration\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurationsaumbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurationsbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\r\n ,\"microsoft.maintenance/publicmaintenanceconfigurations\": { \"SingularDisplayName\": \"Microsoft.Maintenance public maintenance configuration\" }\r\n ,\"microsoft.managedidentity/identities\": { \"SingularDisplayName\": \"Microsoft.ManagedIdentity identity\" }\r\n ,\"microsoft.managedidentity/userassignedidentities\": { \"SingularDisplayName\": \"Managed Identity\" }\r\n ,\"microsoft.managednetwork/managednetworks\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed network\" }\r\n ,\"microsoft.managednetwork/managednetworks/managednetworkgroups\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network group\" }\r\n ,\"microsoft.managednetwork/managednetworks/managednetworkpeeringpolicies\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network peering policy\" }\r\n ,\"microsoft.managednetworkfabric/accesscontrollists\": { \"SingularDisplayName\": \"Access Control List (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/internetgatewayrules\": { \"SingularDisplayName\": \"Internet Gateway Rule (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/internetgateways\": { \"SingularDisplayName\": \"Internet Gateway (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipcommunities\": { \"SingularDisplayName\": \"IP Community (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipextendedcommunities\": { \"SingularDisplayName\": \"IP Extended Community (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipprefixes\": { \"SingularDisplayName\": \"IP Prefix (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l2isolationdomains\": { \"SingularDisplayName\": \"Layer 2 Isolation Domain (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains\": { \"SingularDisplayName\": \"Layer 3 Isolation Domain (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains/externalnetworks\": { \"SingularDisplayName\": \"External Network (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains/internalnetworks\": { \"SingularDisplayName\": \"Internal Network (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/neighborgroups\": { \"SingularDisplayName\": \"Neighbor Group (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkdevices\": { \"SingularDisplayName\": \"Network Device (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkdevices/networkinterfaces\": { \"SingularDisplayName\": \"Network Interface (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabriccontrollers\": { \"SingularDisplayName\": \"Network Fabric Controller (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabrics\": { \"SingularDisplayName\": \"Network Fabric (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabrics/networktonetworkinterconnects\": { \"SingularDisplayName\": \"Network to Network Interconnect (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabricskus\": { \"SingularDisplayName\": \"Network Fabric SKU (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkmonitors\": { \"SingularDisplayName\": \"Microsoft.ManagedNetworkFabric network monitor\" }\r\n ,\"microsoft.managednetworkfabric/networkpacketbrokers\": { \"SingularDisplayName\": \"Network Packet Broker (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkracks\": { \"SingularDisplayName\": \"Network Rack (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networktaprules\": { \"SingularDisplayName\": \"Network Tap Rule (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networktaps\": { \"SingularDisplayName\": \"Network Tap (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/routepolicies\": { \"SingularDisplayName\": \"Route Policy (Operator Nexus)\" }\r\n ,\"microsoft.managedservices/marketplaceregistrationdefinitions\": { \"SingularDisplayName\": \"Microsoft.ManagedServices marketplace registration definition\" }\r\n ,\"microsoft.managedservices/registrationassignments\": { \"SingularDisplayName\": \"Microsoft.ManagedServices registration assignment\" }\r\n ,\"microsoft.managedservices/registrationdefinitions\": { \"SingularDisplayName\": \"Azure Lighthouse\" }\r\n ,\"microsoft.management/managementgroups\": { \"SingularDisplayName\": \"Microsoft.Management management group\" }\r\n ,\"microsoft.management/managementgroups/microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\r\n ,\"microsoft.management/managementgroups/providers/privatelinkassociations\": { \"SingularDisplayName\": \"Application Gateway\" }\r\n ,\"microsoft.management/managementgroups/providers/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\r\n ,\"microsoft.management/managementgroups/settings\": { \"SingularDisplayName\": \"Microsoft.Management management groups setting\" }\r\n ,\"microsoft.management/managementgroups/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Management management groups subscription\" }\r\n ,\"microsoft.management/servicegroups\": { \"SingularDisplayName\": \"Service group\" }\r\n ,\"microsoft.managementpartner/partners\": { \"SingularDisplayName\": \"Microsoft.ManagementPartner partner\" }\r\n ,\"microsoft.manufacturingplatform/manufacturingdataservices\": { \"SingularDisplayName\": \"Factory Operations Agent in Azure AI Foundry\" }\r\n ,\"microsoft.maps/accounts\": { \"SingularDisplayName\": \"Azure Maps Account\" }\r\n ,\"microsoft.maps/accounts/creators\": { \"SingularDisplayName\": \"Azure Maps Creator Resource\" }\r\n ,\"microsoft.marketplace/privatestores\": { \"SingularDisplayName\": \"Microsoft.Marketplace private store\" }\r\n ,\"microsoft.marketplace/privatestores/adminrequestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores admin request approval\" }\r\n ,\"microsoft.marketplace/privatestores/collections\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collection\" }\r\n ,\"microsoft.marketplace/privatestores/collections/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collections offer\" }\r\n ,\"microsoft.marketplace/privatestores/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores offer\" }\r\n ,\"microsoft.marketplace/privatestores/requestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores request approval\" }\r\n ,\"microsoft.media/mediaservices\": { \"SingularDisplayName\": \"Media service\" }\r\n ,\"microsoft.media/mediaservices/accountfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services account filter\" }\r\n ,\"microsoft.media/mediaservices/assets\": { \"SingularDisplayName\": \"Microsoft.Media media services asset\" }\r\n ,\"microsoft.media/mediaservices/assets/assetfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services assets asset filter\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks\": { \"SingularDisplayName\": \"Microsoft.Media media services assets track\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks/operationresults\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation result\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks/operationstatuses\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation statuse\" }\r\n ,\"microsoft.media/mediaservices/contentkeypolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services content key policy\" }\r\n ,\"microsoft.media/mediaservices/liveevents\": { \"SingularDisplayName\": \"Live event\" }\r\n ,\"microsoft.media/mediaservices/liveevents/liveoutputs\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices live events live output\" }\r\n ,\"microsoft.media/mediaservices/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private endpoint connection\" }\r\n ,\"microsoft.media/mediaservices/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private link resource\" }\r\n ,\"microsoft.media/mediaservices/streamingendpoints\": { \"SingularDisplayName\": \"Streaming Endpoint\" }\r\n ,\"microsoft.media/mediaservices/streaminglocators\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming locator\" }\r\n ,\"microsoft.media/mediaservices/streamingpolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming policy\" }\r\n ,\"microsoft.media/mediaservices/transforms\": { \"SingularDisplayName\": \"Microsoft.Media media services transform\" }\r\n ,\"microsoft.media/mediaservices/transforms/jobs\": { \"SingularDisplayName\": \"Microsoft.Media media services transforms job\" }\r\n ,\"microsoft.mesh/worlds\": { \"SingularDisplayName\": \"Microsoft.Mesh world\" }\r\n ,\"microsoft.mesh/worlds/events\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds event\" }\r\n ,\"microsoft.mesh/worlds/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds events access policy\" }\r\n ,\"microsoft.mesh/worlds/spaces\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds space\" }\r\n ,\"microsoft.mesh/worlds/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds spaces access policy\" }\r\n ,\"microsoft.mesh/worlds/templates\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds template\" }\r\n ,\"microsoft.mesh/worlds/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds templates access policy\" }\r\n ,\"microsoft.messagingcatalog/catalogs\": { \"SingularDisplayName\": \"Microsoft.MessagingCatalog catalog\" }\r\n ,\"microsoft.messagingconnectors/connectors\": { \"SingularDisplayName\": \"Microsoft.MessagingConnectors connector\" }\r\n ,\"microsoft.metaverse/metaverses\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverse\" }\r\n ,\"microsoft.metaverse/metaverses/events\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses event\" }\r\n ,\"microsoft.metaverse/metaverses/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses events access policy\" }\r\n ,\"microsoft.metaverse/metaverses/spaces\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses space\" }\r\n ,\"microsoft.metaverse/metaverses/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses spaces access policy\" }\r\n ,\"microsoft.metaverse/metaverses/templates\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses template\" }\r\n ,\"microsoft.metaverse/metaverses/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses templates access policy\" }\r\n ,\"microsoft.migrate/assessmentprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment project\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/clusters\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments cluster\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments avs assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business case\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/avssummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases avs summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedavsmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated avs machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedsqlentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated sql entity\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/iaassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases iaas summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/overviewsummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases overview summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/paassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases paas summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects group\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessments assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessments avs assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql database\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql instance\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/recommendedassessedentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments recommended assessed entity\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments web app service plan\" }\r\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/hypervcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects hypervcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/importcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects importcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/importsqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects import sql collector\" }\r\n ,\"microsoft.migrate/assessmentprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private endpoint connection\" }\r\n ,\"microsoft.migrate/assessmentprojects/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private link resource\" }\r\n ,\"microsoft.migrate/assessmentprojects/projectsummary\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects project summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/servercollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects servercollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql database\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql instance\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sqlcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/vmwarecollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects vmwarecollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments web app service plan\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app collector\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessments summary\" }\r\n ,\"microsoft.migrate/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate project\" }\r\n ,\"microsoft.migrate/migrateprojects/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database instance\" }\r\n ,\"microsoft.migrate/migrateprojects/databases\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database\" }\r\n ,\"microsoft.migrate/migrateprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects machine\" }\r\n ,\"microsoft.migrate/migrateprojects/migrateevents\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects migrate event\" }\r\n ,\"microsoft.migrate/migrateprojects/solutions\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects solution\" }\r\n ,\"microsoft.migrate/modernizeprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize project\" }\r\n ,\"microsoft.migrate/modernizeprojects/deployedresources\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects deployed resource\" }\r\n ,\"microsoft.migrate/modernizeprojects/jobs\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects job\" }\r\n ,\"microsoft.migrate/modernizeprojects/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects jobs operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/migrateagents\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agent\" }\r\n ,\"microsoft.migrate/modernizeprojects/migrateagents/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agents operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployment\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployments operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloadinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instance\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloadinstances/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instances operation\" }\r\n ,\"microsoft.migrate/movecollections\": { \"SingularDisplayName\": \"Microsoft.Migrate move collection\" }\r\n ,\"microsoft.migrate/movecollections/moveresources\": { \"SingularDisplayName\": \"Microsoft.Migrate move collections move resource\" }\r\n ,\"microsoft.migrate/projects\": { \"SingularDisplayName\": \"Migration project\" }\r\n ,\"microsoft.mission/approvals\": { \"SingularDisplayName\": \"Approval\" }\r\n ,\"microsoft.mission/catalogs\": { \"SingularDisplayName\": \"Catalog\" }\r\n ,\"microsoft.mission/communities\": { \"SingularDisplayName\": \"Community\" }\r\n ,\"microsoft.mission/communities/communityendpoints\": { \"SingularDisplayName\": \"Community endpoint\" }\r\n ,\"microsoft.mission/communities/transithubs\": { \"SingularDisplayName\": \"Transit hub\" }\r\n ,\"microsoft.mission/enclaveconnections\": { \"SingularDisplayName\": \"Enclave connection\" }\r\n ,\"microsoft.mission/externalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission external connection\" }\r\n ,\"microsoft.mission/internalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission internal connection\" }\r\n ,\"microsoft.mission/virtualenclaves\": { \"SingularDisplayName\": \"Enclave\" }\r\n ,\"microsoft.mission/virtualenclaves/enclaveendpoints\": { \"SingularDisplayName\": \"Enclave endpoint\" }\r\n ,\"microsoft.mission/virtualenclaves/endpoints\": { \"SingularDisplayName\": \"Endpoint\" }\r\n ,\"microsoft.mission/virtualenclaves/workloads\": { \"SingularDisplayName\": \"Workload\" }\r\n ,\"microsoft.mixedreality/objectanchorsaccounts\": { \"SingularDisplayName\": \"Object Anchors Account\" }\r\n ,\"microsoft.mixedreality/objectunderstandingaccounts\": { \"SingularDisplayName\": \"Object Understanding Account\" }\r\n ,\"microsoft.mixedreality/remoterenderingaccounts\": { \"SingularDisplayName\": \"Remote Rendering Account\" }\r\n ,\"microsoft.mixedreality/spatialanchorsaccounts\": { \"SingularDisplayName\": \"Spatial Anchors Account\" }\r\n ,\"microsoft.mixedreality/spatialmapsaccounts\": { \"SingularDisplayName\": \"Microsoft.MixedReality spatial maps account\" }\r\n ,\"microsoft.mobilenetwork/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork amf deployment\" }\r\n ,\"microsoft.mobilenetwork/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork cluster service\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks\": { \"SingularDisplayName\": \"Mobile Network\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/datanetworks\": { \"SingularDisplayName\": \"Data Network\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/services\": { \"SingularDisplayName\": \"Service\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/simpolicies\": { \"SingularDisplayName\": \"SIM Policy\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/sites\": { \"SingularDisplayName\": \"Mobile Network Site\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/slices\": { \"SingularDisplayName\": \"Slice\" }\r\n ,\"microsoft.mobilenetwork/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nrf deployment\" }\r\n ,\"microsoft.mobilenetwork/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nssf deployment\" }\r\n ,\"microsoft.mobilenetwork/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork observability service\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes\": { \"SingularDisplayName\": \"Packet Core Control Plane\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes\": { \"SingularDisplayName\": \"Packet Core Data Plane\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes/attacheddatanetworks\": { \"SingularDisplayName\": \"Attached Data Network\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplaneversions\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork packet core control plane version\" }\r\n ,\"microsoft.mobilenetwork/radioaccessnetworks\": { \"SingularDisplayName\": \"Radio Access Network Insights\" }\r\n ,\"microsoft.mobilenetwork/sdmdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sdm deployment\" }\r\n ,\"microsoft.mobilenetwork/simgroups\": { \"SingularDisplayName\": \"SIM Group\" }\r\n ,\"microsoft.mobilenetwork/simgroups/sims\": { \"SingularDisplayName\": \"SIM\" }\r\n ,\"microsoft.mobilenetwork/sims\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sim\" }\r\n ,\"microsoft.mobilenetwork/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork smf deployment\" }\r\n ,\"microsoft.mobilenetwork/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork upf deployment\" }\r\n ,\"microsoft.mobilenetwork/virtualizedmmedeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork virtualized mme deployment\" }\r\n ,\"microsoft.mobilenetwork/vnfagentdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork vnf agent deployment\" }\r\n ,\"microsoft.mobilepacketcore/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore amf deployment\" }\r\n ,\"microsoft.mobilepacketcore/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore cluster service\" }\r\n ,\"microsoft.mobilepacketcore/networkfunctions\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore network function\" }\r\n ,\"microsoft.mobilepacketcore/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nrf deployment\" }\r\n ,\"microsoft.mobilepacketcore/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nssf deployment\" }\r\n ,\"microsoft.mobilepacketcore/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore observability service\" }\r\n ,\"microsoft.mobilepacketcore/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore smf deployment\" }\r\n ,\"microsoft.mobilepacketcore/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore upf deployment\" }\r\n ,\"microsoft.modsimworkbench/workbenches\": { \"SingularDisplayName\": \"Modeling and Simulation Workbench\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers\": { \"SingularDisplayName\": \"Chamber\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/connectors\": { \"SingularDisplayName\": \"Chamber Connector\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/filerequests\": { \"SingularDisplayName\": \"Chamber Data Pipeline File Request\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/files\": { \"SingularDisplayName\": \"Chamber Data Pipeline File\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/licenses\": { \"SingularDisplayName\": \"Chamber License\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/storages\": { \"SingularDisplayName\": \"Chamber Storage\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/workloads\": { \"SingularDisplayName\": \"Chamber VM\" }\r\n ,\"microsoft.modsimworkbench/workbenches/sharedstorages\": { \"SingularDisplayName\": \"Shared Storage\" }\r\n ,\"microsoft.monitor/accounts\": { \"SingularDisplayName\": \"Azure Monitor workspace\" }\r\n ,\"microsoft.monitor/investigations\": { \"SingularDisplayName\": \"Microsoft.Monitor investigation\" }\r\n ,\"microsoft.monitor/pipelinegroups\": { \"SingularDisplayName\": \"Azure Monitor pipeline\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsite\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites agent\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites error summary\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/mysqlservers\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites my sqlserver\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/summaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites summary\" }\r\n ,\"microsoft.netapp/netappaccounts\": { \"SingularDisplayName\": \"NetApp account\" }\r\n ,\"microsoft.netapp/netappaccounts/backuppolicies\": { \"SingularDisplayName\": \"Backup Policy\" }\r\n ,\"microsoft.netapp/netappaccounts/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools\": { \"SingularDisplayName\": \"Capacity pool\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes\": { \"SingularDisplayName\": \"Volume\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/volumequotarules\": { \"SingularDisplayName\": \"User and group quota\" }\r\n ,\"microsoft.netapp/netappaccounts/snapshotpolicies\": { \"SingularDisplayName\": \"Snapshot policy\" }\r\n ,\"microsoft.netapp/netappaccounts/volumegroups\": { \"SingularDisplayName\": \"VolumeGroup\" }\r\n ,\"microsoft.network/applicationgatewayavailablessloptions\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl option\" }\r\n ,\"microsoft.network/applicationgatewayavailablessloptions/predefinedpolicies\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl options predefined policy\" }\r\n ,\"microsoft.network/applicationgateways\": { \"SingularDisplayName\": \"Application gateway\" }\r\n ,\"microsoft.network/applicationgatewaywebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Application Gateway WAF policy\" }\r\n ,\"microsoft.network/applicationsecuritygroups\": { \"SingularDisplayName\": \"Application security group\" }\r\n ,\"microsoft.network/azurefirewalls\": { \"SingularDisplayName\": \"Firewall\" }\r\n ,\"microsoft.network/azurewebcategories\": { \"SingularDisplayName\": \"Microsoft.Network Azure web category\" }\r\n ,\"microsoft.network/bastionhosts\": { \"SingularDisplayName\": \"Bastion\" }\r\n ,\"microsoft.network/cloudserviceslots\": { \"SingularDisplayName\": \"Microsoft.Network cloud service slot\" }\r\n ,\"microsoft.network/connections\": { \"SingularDisplayName\": \"Connection\" }\r\n ,\"microsoft.network/customipprefixes\": { \"SingularDisplayName\": \"Custom IP Prefix\" }\r\n ,\"microsoft.network/ddoscustompolicies\": { \"SingularDisplayName\": \"Microsoft.Network DDoS custom policy\" }\r\n ,\"microsoft.network/ddosprotectionplans\": { \"SingularDisplayName\": \"DDoS protection plan\" }\r\n ,\"microsoft.network/dnsforwardingrulesets\": { \"SingularDisplayName\": \"DNS forwarding ruleset\" }\r\n ,\"microsoft.network/dnsresolverdomainlists\": { \"SingularDisplayName\": \"DNS Domain List\" }\r\n ,\"microsoft.network/dnsresolverpolicies\": { \"SingularDisplayName\": \"DNS Security Policy\" }\r\n ,\"microsoft.network/dnsresolvers\": { \"SingularDisplayName\": \"DNS private resolver\" }\r\n ,\"microsoft.network/dnszones\": { \"SingularDisplayName\": \"DNS zone\" }\r\n ,\"microsoft.network/dscpconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network DSCP configuration\" }\r\n ,\"microsoft.network/expressroutecircuits\": { \"SingularDisplayName\": \"ExpressRoute circuit\" }\r\n ,\"microsoft.network/expressroutecrossconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connection\" }\r\n ,\"microsoft.network/expressroutecrossconnections/peerings\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connections peering\" }\r\n ,\"microsoft.network/expressroutegateways\": { \"SingularDisplayName\": \"ExpressRoute Gateway\" }\r\n ,\"microsoft.network/expressroutegateways/expressrouteconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route gateways express route connection\" }\r\n ,\"microsoft.network/expressrouteports\": { \"SingularDisplayName\": \"ExpressRoute Direct\" }\r\n ,\"microsoft.network/expressrouteportslocations\": { \"SingularDisplayName\": \"Microsoft.Network express route ports location\" }\r\n ,\"microsoft.network/firewallpolicies\": { \"SingularDisplayName\": \"Firewall Policy\" }\r\n ,\"microsoft.network/frontdoors\": { \"SingularDisplayName\": \"Front Door and CDN profiles\" }\r\n ,\"microsoft.network/frontdoorwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Front Door WAF policy\" }\r\n ,\"microsoft.network/ipallocations\": { \"SingularDisplayName\": \"Microsoft.Network IP allocation\" }\r\n ,\"microsoft.network/ipgroups\": { \"SingularDisplayName\": \"IP Group\" }\r\n ,\"microsoft.network/loadbalancers\": { \"SingularDisplayName\": \"Load balancer\" }\r\n ,\"microsoft.network/localnetworkgateways\": { \"SingularDisplayName\": \"Local network gateway\" }\r\n ,\"microsoft.network/natgateways\": { \"SingularDisplayName\": \"NAT gateway\" }\r\n ,\"microsoft.network/networkexperimentprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profile\" }\r\n ,\"microsoft.network/networkexperimentprofiles/experiments\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profiles experiment\" }\r\n ,\"microsoft.network/networkinterfaces\": { \"SingularDisplayName\": \"Network interface\" }\r\n ,\"microsoft.network/networkmanagerconnections\": { \"SingularDisplayName\": \"Microsoft.Network network manager connection\" }\r\n ,\"microsoft.network/networkmanagers\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/connectivityconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/ipampools\": { \"SingularDisplayName\": \"IP address pool\" }\r\n ,\"microsoft.network/networkmanagers/networkgroups\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/routingconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/securityadminconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/securityuserconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/verifierworkspaces\": { \"SingularDisplayName\": \"Verifier Workspace\" }\r\n ,\"microsoft.network/networkprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network profile\" }\r\n ,\"microsoft.network/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group\" }\r\n ,\"microsoft.network/networksecurityperimeters\": { \"SingularDisplayName\": \"Network Security Perimeter\" }\r\n ,\"microsoft.network/networksecurityperimeters/profiles\": { \"SingularDisplayName\": \"Network Security Perimeter Profile\" }\r\n ,\"microsoft.network/networkverifiers\": { \"SingularDisplayName\": \"Virtual Network Verifier\" }\r\n ,\"microsoft.network/networkvirtualappliances\": { \"SingularDisplayName\": \"Microsoft.Network network virtual appliance\" }\r\n ,\"microsoft.network/networkwatchers\": { \"SingularDisplayName\": \"Network Watcher\" }\r\n ,\"microsoft.network/networkwatchers/flowlogs\": { \"SingularDisplayName\": \"Flow log\" }\r\n ,\"microsoft.network/p2svpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Point to Site)\" }\r\n ,\"microsoft.network/privatednszones\": { \"SingularDisplayName\": \"Private DNS zone\" }\r\n ,\"microsoft.network/privatednszones/virtualnetworklinks\": { \"SingularDisplayName\": \"Virtual network link\" }\r\n ,\"microsoft.network/privateendpoints\": { \"SingularDisplayName\": \"Private endpoint\" }\r\n ,\"microsoft.network/privatelinkservices\": { \"SingularDisplayName\": \"Private link service\" }\r\n ,\"microsoft.network/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\r\n ,\"microsoft.network/publicipprefixes\": { \"SingularDisplayName\": \"Public IP Prefix\" }\r\n ,\"microsoft.network/routefilters\": { \"SingularDisplayName\": \"Route filter\" }\r\n ,\"microsoft.network/routetables\": { \"SingularDisplayName\": \"Route table\" }\r\n ,\"microsoft.network/securitypartnerproviders\": { \"SingularDisplayName\": \"Microsoft.Network security partner provider\" }\r\n ,\"microsoft.network/serviceendpointpolicies\": { \"SingularDisplayName\": \"Service endpoint policy\" }\r\n ,\"microsoft.network/trafficmanagergeographichierarchies\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager geographic hierarchy\" }\r\n ,\"microsoft.network/trafficmanagerprofiles\": { \"SingularDisplayName\": \"Traffic Manager profile\" }\r\n ,\"microsoft.network/trafficmanagerusermetricskeys\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager user metrics key\" }\r\n ,\"microsoft.network/virtualhubs\": { \"SingularDisplayName\": \"Microsoft.Network/virtualHub\" }\r\n ,\"microsoft.network/virtualnetworkgateways\": { \"SingularDisplayName\": \"Virtual network gateway\" }\r\n ,\"microsoft.network/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network\" }\r\n ,\"microsoft.network/virtualnetworktaps\": { \"SingularDisplayName\": \"Virtual network terminal access point\" }\r\n ,\"microsoft.network/virtualrouters\": { \"SingularDisplayName\": \"Microsoft.Network virtual router\" }\r\n ,\"microsoft.network/virtualrouters/peerings\": { \"SingularDisplayName\": \"Microsoft.Network virtual routers peering\" }\r\n ,\"microsoft.network/virtualwans\": { \"SingularDisplayName\": \"Virtual WAN\" }\r\n ,\"microsoft.network/vpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Site to Site)\" }\r\n ,\"microsoft.network/vpngateways/vpnconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connection\" }\r\n ,\"microsoft.network/vpngateways/vpnconnections/vpnlinkconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connections VPN link connection\" }\r\n ,\"microsoft.network/vpnserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network VPN server configuration\" }\r\n ,\"microsoft.network/vpnsites\": { \"SingularDisplayName\": \"Microsoft.Network VPN site\" }\r\n ,\"microsoft.network/vpnsites/vpnsitelinks\": { \"SingularDisplayName\": \"Microsoft.Network VPN sites VPN site link\" }\r\n ,\"microsoft.networkanalytics/dataconnectors\": { \"SingularDisplayName\": \"AIOps - Data Connector\" }\r\n ,\"microsoft.networkanalytics/datalakehouses\": { \"SingularDisplayName\": \"AIOps - Data LakeHouse\" }\r\n ,\"microsoft.networkanalytics/dataproducts\": { \"SingularDisplayName\": \"Azure Operator Insights ? Data Product\" }\r\n ,\"microsoft.networkanalytics/dataproducts/datatypes\": { \"SingularDisplayName\": \"Data Type\" }\r\n ,\"microsoft.networkanalytics/dataproductscatalogs\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics data products catalog\" }\r\n ,\"microsoft.networkanalytics/metricsingestionendpoints\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics metrics ingestion endpoint\" }\r\n ,\"microsoft.networkanalytics/networkanalyticsproducts\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics network analytics product\" }\r\n ,\"microsoft.networkcloud/baremetalmachines\": { \"SingularDisplayName\": \"Bare Metal Machine (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/cloudservicesnetworks\": { \"SingularDisplayName\": \"Cloud Services Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clustermanagers\": { \"SingularDisplayName\": \"Cluster Manager (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters\": { \"SingularDisplayName\": \"Cluster (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/baremetalmachinekeysets\": { \"SingularDisplayName\": \"Cluster Bare Metal Machine Key Set (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/bmckeysets\": { \"SingularDisplayName\": \"Cluster Baseboard Management Controller Key Set (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/metricsconfigurations\": { \"SingularDisplayName\": \"Cluster Metrics Configuration (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/edgeclustermachineskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster machine SKU\" }\r\n ,\"microsoft.networkcloud/edgeclusterruntimeversions\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster runtime version\" }\r\n ,\"microsoft.networkcloud/edgeclusters\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster\" }\r\n ,\"microsoft.networkcloud/edgeclusters/nodes\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge clusters node\" }\r\n ,\"microsoft.networkcloud/edgeclusterskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster SKU\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters\": { \"SingularDisplayName\": \"Kubernetes Cluster (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters/agentpools\": { \"SingularDisplayName\": \"Agent Pool (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters/features\": { \"SingularDisplayName\": \"Kubernetes Cluster Feature (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/l2networks\": { \"SingularDisplayName\": \"Layer 2 Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/l3networks\": { \"SingularDisplayName\": \"Layer 3 Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/racks\": { \"SingularDisplayName\": \"Compute Rack (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/rackskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud rack SKU\" }\r\n ,\"microsoft.networkcloud/registrationhubs\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hub\" }\r\n ,\"microsoft.networkcloud/registrationhubs/images\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs image\" }\r\n ,\"microsoft.networkcloud/registrationhubs/machines\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs machine\" }\r\n ,\"microsoft.networkcloud/storageappliances\": { \"SingularDisplayName\": \"Storage Appliance (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/trunkednetworks\": { \"SingularDisplayName\": \"Trunked Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/virtualmachines\": { \"SingularDisplayName\": \"Virtual Machine (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/virtualmachines/consoles\": { \"SingularDisplayName\": \"Virtual Machine Console (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/volumes\": { \"SingularDisplayName\": \"Volume (Operator Nexus)\" }\r\n ,\"microsoft.networkfunction/azuretrafficcollectors\": { \"SingularDisplayName\": \"ExpressRoute traffic collector\" }\r\n ,\"microsoft.networkfunction/meshvpns\": { \"SingularDisplayName\": \"Mesh VPN\" }\r\n ,\"microsoft.nexusidentity/identitycontrollers\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity controller\" }\r\n ,\"microsoft.nexusidentity/identitysets\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity set\" }\r\n ,\"microsoft.notebooks/notebookproxies\": { \"SingularDisplayName\": \"Microsoft.Notebooks notebook proxy\" }\r\n ,\"microsoft.notificationhubs/namespaces\": { \"SingularDisplayName\": \"Notification Hub Namespace\" }\r\n ,\"microsoft.notificationhubs/namespaces/notificationhubs\": { \"SingularDisplayName\": \"Notification Hub\" }\r\n ,\"microsoft.objectstore/osnamespaces\": { \"SingularDisplayName\": \"Microsoft.ObjectStore os namespace\" }\r\n })[tolower(id)]\r\n}\r\n", - "$fxv#3": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_4(id: string) {\r\n dynamic({\r\n \"microsoft.offazure/hypervsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv site\" }\r\n ,\"microsoft.offazure/hypervsites/clusters\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites cluster\" }\r\n ,\"microsoft.offazure/hypervsites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites host\" }\r\n ,\"microsoft.offazure/hypervsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites job\" }\r\n ,\"microsoft.offazure/hypervsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machine\" }\r\n ,\"microsoft.offazure/hypervsites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machines software inventory\" }\r\n ,\"microsoft.offazure/hypervsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites operations statu\" }\r\n ,\"microsoft.offazure/hypervsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites run as account\" }\r\n ,\"microsoft.offazure/importsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure import site\" }\r\n ,\"microsoft.offazure/importsites/deletejobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites delete job\" }\r\n ,\"microsoft.offazure/importsites/exportjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites export job\" }\r\n ,\"microsoft.offazure/importsites/importjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites import job\" }\r\n ,\"microsoft.offazure/importsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites job\" }\r\n ,\"microsoft.offazure/importsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites machine\" }\r\n ,\"microsoft.offazure/mastersites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master site\" }\r\n ,\"microsoft.offazure/mastersites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites operations statu\" }\r\n ,\"microsoft.offazure/mastersites/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private endpoint connection\" }\r\n ,\"microsoft.offazure/mastersites/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private link resource\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql site\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites discovery site data source\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites job\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites operations statu\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites run as account\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqlavailabilitygroups\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql availability group\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqldatabases\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql database\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqlservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql server\" }\r\n ,\"microsoft.offazure/mastersites/webappsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app site\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites discovery site data source\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/extendedmachines\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites extended machine\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/iiswebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web application\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/iiswebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web server\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites runasaccount\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web application\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web server\" }\r\n ,\"microsoft.offazure/serversites\": { \"SingularDisplayName\": \"Microsoft.OffAzure server site\" }\r\n ,\"microsoft.offazure/serversites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites job\" }\r\n ,\"microsoft.offazure/serversites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machine\" }\r\n ,\"microsoft.offazure/serversites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machines software inventory\" }\r\n ,\"microsoft.offazure/serversites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites operations statu\" }\r\n ,\"microsoft.offazure/serversites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites run as account\" }\r\n ,\"microsoft.offazure/vmwaresites\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware site\" }\r\n ,\"microsoft.offazure/vmwaresites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites host\" }\r\n ,\"microsoft.offazure/vmwaresites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites job\" }\r\n ,\"microsoft.offazure/vmwaresites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machine\" }\r\n ,\"microsoft.offazure/vmwaresites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machines software inventory\" }\r\n ,\"microsoft.offazure/vmwaresites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites operations statu\" }\r\n ,\"microsoft.offazure/vmwaresites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites run as account\" }\r\n ,\"microsoft.offazure/vmwaresites/vcenters\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites vcenter\" }\r\n ,\"microsoft.offazurespringboot/springbootsites\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsite\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites error summary\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/springbootapps\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootapp\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/springbootservers\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootserver\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/summaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites summary\" }\r\n ,\"microsoft.onlineexperimentation/workspaces\": { \"SingularDisplayName\": \"Online Experimentation Workspace\" }\r\n ,\"microsoft.openenergyplatform/energyservices\": { \"SingularDisplayName\": \"Azure Data Manager for Energy\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspace\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/applicationregistrations\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application registration\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/applications\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/eventgridfilters\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces event grid filter\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/shares\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/sharesubscriptions\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share subscription\" }\r\n ,\"microsoft.operationalinsights/clusters\": { \"SingularDisplayName\": \"Log Analytics dedicated cluster\" }\r\n ,\"microsoft.operationalinsights/querypacks\": { \"SingularDisplayName\": \"Log Analytics query pack\" }\r\n ,\"microsoft.operationalinsights/workspaces\": { \"SingularDisplayName\": \"Log Analytics workspace\" }\r\n ,\"microsoft.operationsmanagement/managementassociations\": { \"SingularDisplayName\": \"Microsoft.OperationsManagement management association\" }\r\n ,\"microsoft.operationsmanagement/solutions\": { \"SingularDisplayName\": \"Solution\" }\r\n ,\"microsoft.operatorvoicemail/operatorvoicemailinstances\": { \"SingularDisplayName\": \"Microsoft.OperatorVoicemail operator voicemail instance\" }\r\n ,\"microsoft.oraclediscovery/oraclesites\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle site\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites error summary\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/oracledatabases\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle database\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/oracleservers\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle server\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/summaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites summary\" }\r\n ,\"microsoft.orbital/cloudaccessrouters\": { \"SingularDisplayName\": \"Cloud Access Router\" }\r\n ,\"microsoft.orbital/contactprofiles\": { \"SingularDisplayName\": \"Contact Profile\" }\r\n ,\"microsoft.orbital/edgesites\": { \"SingularDisplayName\": \"Edge Site\" }\r\n ,\"microsoft.orbital/geocatalogs\": { \"SingularDisplayName\": \"GeoCatalog\" }\r\n ,\"microsoft.orbital/globalcommunicationssites\": { \"SingularDisplayName\": \"Microsoft.Orbital global communications site\" }\r\n ,\"microsoft.orbital/groundstations\": { \"SingularDisplayName\": \"Ground Station\" }\r\n ,\"microsoft.orbital/l2connections\": { \"SingularDisplayName\": \"L2 Connection\" }\r\n ,\"microsoft.orbital/sdwancontrollers\": { \"SingularDisplayName\": \"SDWAN Controller\" }\r\n ,\"microsoft.orbital/spacecrafts\": { \"SingularDisplayName\": \"Spacecraft\" }\r\n ,\"microsoft.orbital/spacecrafts/contacts\": { \"SingularDisplayName\": \"Contact\" }\r\n ,\"microsoft.orbital/terminals\": { \"SingularDisplayName\": \"Cloud Access Terminal\" }\r\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrence\" }\r\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences/operationresult\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrences operation result\" }\r\n ,\"microsoft.peering/peerasns\": { \"SingularDisplayName\": \"Microsoft.Peering peer asn\" }\r\n ,\"microsoft.peering/peerings\": { \"SingularDisplayName\": \"Peering\" }\r\n ,\"microsoft.peering/peerings/registeredasns\": { \"SingularDisplayName\": \"Registered ASN\" }\r\n ,\"microsoft.peering/peerings/registeredprefixes\": { \"SingularDisplayName\": \"Registered prefix\" }\r\n ,\"microsoft.peering/peeringservices\": { \"SingularDisplayName\": \"Peering Service\" }\r\n ,\"microsoft.peering/peeringservices/prefixes\": { \"SingularDisplayName\": \"Peering Service Prefix\" }\r\n ,\"microsoft.pki/pkis\": { \"SingularDisplayName\": \"Microsoft.Pki PKI\" }\r\n ,\"microsoft.pki/pkis/certificateauthorities\": { \"SingularDisplayName\": \"Microsoft.Pki pkis certificate authority\" }\r\n ,\"microsoft.pki/pkis/enrollmentpolicies\": { \"SingularDisplayName\": \"Microsoft.Pki pkis enrollment policy\" }\r\n ,\"microsoft.policyinsights/attestations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights attestation\" }\r\n ,\"microsoft.policyinsights/policymetadata\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights policy metadata\" }\r\n ,\"microsoft.policyinsights/remediations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights remediation\" }\r\n ,\"microsoft.portal/consoles\": { \"SingularDisplayName\": \"Microsoft.Portal console\" }\r\n ,\"microsoft.portal/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\r\n ,\"microsoft.portal/tenantconfigurations\": { \"SingularDisplayName\": \"Microsoft.Portal tenant configuration\" }\r\n ,\"microsoft.portal/usersettings\": { \"SingularDisplayName\": \"Microsoft.Portal user setting\" }\r\n ,\"microsoft.portal/virtual-privatedashboards\": { \"SingularDisplayName\": \"Private dashboard\" }\r\n ,\"microsoft.portalservices/copilotsettings\": { \"SingularDisplayName\": \"Microsoft.PortalServices copilot setting\" }\r\n ,\"microsoft.portalservices/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\r\n ,\"microsoft.portalservices/extensions\": { \"SingularDisplayName\": \"Portal Extension\" }\r\n ,\"microsoft.portalservices/extensions/deployments\": { \"SingularDisplayName\": \"Extension Deployment\" }\r\n ,\"microsoft.portalservices/extensions/slots\": { \"SingularDisplayName\": \"Extension Slot\" }\r\n ,\"microsoft.portalservices/extensions/versions\": { \"SingularDisplayName\": \"Extension Version\" }\r\n ,\"microsoft.portalservices/settings\": { \"SingularDisplayName\": \"Microsoft.PortalServices setting\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private endpoint connection\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private link resource\" }\r\n ,\"microsoft.powerbi/workspacecollections\": { \"SingularDisplayName\": \"Microsoft.PowerBI workspace collection\" }\r\n ,\"microsoft.powerbidedicated/autoscalevcores\": { \"SingularDisplayName\": \"Microsoft.PowerBIDedicated auto scale vcore\" }\r\n ,\"microsoft.powerbidedicated/capacities\": { \"SingularDisplayName\": \"Power BI Embedded\" }\r\n ,\"microsoft.powerplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.PowerPlatform account\" }\r\n ,\"microsoft.premonition/libraries\": { \"SingularDisplayName\": \"Microsoft.Premonition library\" }\r\n ,\"microsoft.premonition/libraries/analyses\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries analyse\" }\r\n ,\"microsoft.premonition/libraries/samples\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries sample\" }\r\n ,\"microsoft.professionalservice/resources\": { \"SingularDisplayName\": \"Professional Service\" }\r\n ,\"microsoft.programmableconnectivity/gateways\": { \"SingularDisplayName\": \"APC Gateway\" }\r\n ,\"microsoft.programmableconnectivity/operatorapiconnections\": { \"SingularDisplayName\": \"APC Operator API Connection\" }\r\n ,\"microsoft.programmableconnectivity/operatorapiplans\": { \"SingularDisplayName\": \"APC Operator API Plan\" }\r\n ,\"microsoft.proposal/proposals\": { \"SingularDisplayName\": \"Microsoft.Proposal proposal\" }\r\n ,\"microsoft.providerhub/providerregistrations\": { \"SingularDisplayName\": \"Resource Provider as a Service\" }\r\n ,\"microsoft.providerhub/providerregistrations/customrollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.providerhub/providerregistrations/defaultrollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\r\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\r\n ,\"microsoft.providerhubdevtest/regionalstresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest regional stresstest\" }\r\n ,\"microsoft.providerhubdevtest/stresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest stresstest\" }\r\n ,\"microsoft.purview/accounts\": { \"SingularDisplayName\": \"Microsoft Purview account\" }\r\n ,\"microsoft.quantum/provideraccounts\": { \"SingularDisplayName\": \"Microsoft.Quantum provider account\" }\r\n ,\"microsoft.quantum/workspaces\": { \"SingularDisplayName\": \"Quantum Workspace\" }\r\n ,\"microsoft.quota/groupquotas\": { \"SingularDisplayName\": \"Microsoft.Quota group quota\" }\r\n ,\"microsoft.quota/groupquotas/groupquotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas group quota request\" }\r\n ,\"microsoft.quota/groupquotas/quotaallocationrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation request\" }\r\n ,\"microsoft.quota/groupquotas/quotaallocations\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation\" }\r\n ,\"microsoft.quota/groupquotas/subscriptionrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription request\" }\r\n ,\"microsoft.quota/groupquotas/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription\" }\r\n ,\"microsoft.quota/quotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota quota request\" }\r\n ,\"microsoft.quota/quotas\": { \"SingularDisplayName\": \"Microsoft.Quota quota\" }\r\n ,\"microsoft.quota/usages\": { \"SingularDisplayName\": \"Microsoft.Quota usage\" }\r\n ,\"microsoft.recommendationsservice/accounts\": { \"SingularDisplayName\": \"Intelligent Recommendations Account\" }\r\n ,\"microsoft.recommendationsservice/accounts/modeling\": { \"SingularDisplayName\": \"Modeling\" }\r\n ,\"microsoft.recommendationsservice/accounts/serviceendpoints\": { \"SingularDisplayName\": \"Service Endpoint\" }\r\n ,\"microsoft.recoveryservices/replicationeligibilityresults\": { \"SingularDisplayName\": \"Microsoft.RecoveryServices replication eligibility result\" }\r\n ,\"microsoft.recoveryservices/vaults\": { \"SingularDisplayName\": \"Recovery Services vault\" }\r\n ,\"microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems\": { \"SingularDisplayName\": \"Backup Item\" }\r\n ,\"microsoft.recoveryservicesbvtd/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD\" }\r\n ,\"microsoft.recoveryservicesbvtd2/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD2\" }\r\n ,\"microsoft.recoveryservicesintd/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD\" }\r\n ,\"microsoft.recoveryservicesintd2/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD2\" }\r\n ,\"microsoft.redhatopenshift/openshiftclusters\": { \"SingularDisplayName\": \"Azure Red Hat OpenShift cluster\" }\r\n ,\"microsoft.relationships/dependencyof\": { \"SingularDisplayName\": \"Dependency Relationship\" }\r\n ,\"microsoft.relationships/servicegroupmember\": { \"SingularDisplayName\": \"Service group member relationship\" }\r\n ,\"microsoft.relationships/servicegrouprelationships\": { \"SingularDisplayName\": \"Connected Resource\" }\r\n ,\"microsoft.relay/namespaces\": { \"SingularDisplayName\": \"Relay\" }\r\n ,\"microsoft.relay/namespaces/hybridconnections\": { \"SingularDisplayName\": \"Hybrid connection\" }\r\n ,\"microsoft.relay/namespaces/wcfrelays\": { \"SingularDisplayName\": \"WCF relay\" }\r\n ,\"microsoft.resilience/resiliencestates\": { \"SingularDisplayName\": \"Microsoft.Resilience resilience state\" }\r\n ,\"microsoft.resourceconnector/appliances\": { \"SingularDisplayName\": \"Resource bridge\" }\r\n ,\"microsoft.resourcegraph/queries\": { \"SingularDisplayName\": \"Resource Graph query\" }\r\n ,\"microsoft.resourcehealth/availabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth availability statuse\" }\r\n ,\"microsoft.resourcehealth/childavailabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth child availability statuse\" }\r\n ,\"microsoft.resourcehealth/emergingissues\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth emerging issue\" }\r\n ,\"microsoft.resourcehealth/events\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth event\" }\r\n ,\"microsoft.resourcehealth/events/impactedresources\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth events impacted resource\" }\r\n ,\"microsoft.resourcehealth/metadata\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth metadata\" }\r\n ,\"microsoft.resources/builtintemplatespecs\": { \"SingularDisplayName\": \"Built-in template spec\" }\r\n ,\"microsoft.resources/changes\": { \"SingularDisplayName\": \"Microsoft.Resources change\" }\r\n ,\"microsoft.resources/databoundaries\": { \"SingularDisplayName\": \"Microsoft.Resources data boundary\" }\r\n ,\"microsoft.resources/deletedresources\": { \"SingularDisplayName\": \"Recycle Bin\" }\r\n ,\"microsoft.resources/deployments\": { \"SingularDisplayName\": \"Microsoft.Resources deployment\" }\r\n ,\"microsoft.resources/deployments/operations\": { \"SingularDisplayName\": \"Microsoft.Resources deployments operation\" }\r\n ,\"microsoft.resources/deploymentscripts\": { \"SingularDisplayName\": \"Deployment Script\" }\r\n ,\"microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\r\n ,\"microsoft.resources/mobobrokers\": { \"SingularDisplayName\": \"Microsoft.Resources mobo broker\" }\r\n ,\"microsoft.resources/resourcechange\": { \"SingularDisplayName\": \"Change Analysis\" }\r\n ,\"microsoft.resources/resourcechanges\": { \"SingularDisplayName\": \"Resource change\" }\r\n ,\"microsoft.resources/resourcegraphvisualizer\": { \"SingularDisplayName\": \"Resource Graph Visualizer\" }\r\n ,\"microsoft.resources/resourcegroups\": { \"SingularDisplayName\": \"Microsoft.Resources resource group\" }\r\n ,\"microsoft.resources/resources\": { \"SingularDisplayName\": \"Resource\" }\r\n ,\"microsoft.resources/snapshots\": { \"SingularDisplayName\": \"Microsoft.Resources snapshot\" }\r\n ,\"microsoft.resources/subscriptions\": { \"SingularDisplayName\": \"Subscription\" }\r\n ,\"microsoft.resources/subscriptions/resourcegroups\": { \"SingularDisplayName\": \"Resource group\" }\r\n ,\"microsoft.resources/tags\": { \"SingularDisplayName\": \"Microsoft.Resources tag\" }\r\n ,\"microsoft.resources/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\r\n ,\"microsoft.resources/virtualsubscriptionsforresourcepicker\": { \"SingularDisplayName\": \"Subscription\" }\r\n ,\"microsoft.saas/applications\": { \"SingularDisplayName\": \"Software as a Service (classic)\" }\r\n ,\"microsoft.saas/resources\": { \"SingularDisplayName\": \"SaaS\" }\r\n ,\"microsoft.saas/saasresources\": { \"SingularDisplayName\": \"SaaS (classic)\" }\r\n ,\"microsoft.saashub/cloudservices\": { \"SingularDisplayName\": \"Microsoft.SaaSHub cloud service\" }\r\n ,\"microsoft.saashub/cloudservices/hidden\": { \"SingularDisplayName\": \"Microsoft SaaS\" }\r\n ,\"microsoft.saashub/saasresources\": { \"SingularDisplayName\": \"Microsoft.SaaSHub saas resource\" }\r\n ,\"microsoft.salescopilot/conversationintelligencerecordingaccounts\": { \"SingularDisplayName\": \"Microsoft.SalesCopilot conversation intelligence recording account\" }\r\n ,\"microsoft.scheduler/jobcollections\": { \"SingularDisplayName\": \"Scheduler job collection\" }\r\n ,\"microsoft.scheduler/jobcollections/jobs\": { \"SingularDisplayName\": \"Scheduler job\" }\r\n ,\"microsoft.scom/managedinstances\": { \"SingularDisplayName\": \"SCOM managed instance\" }\r\n ,\"microsoft.scvmm/availabilitysets\": { \"SingularDisplayName\": \"Microsoft.ScVmm availability set\" }\r\n ,\"microsoft.scvmm/clouds\": { \"SingularDisplayName\": \"Microsoft.ScVmm cloud\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instance\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances guest agent\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.scvmm/virtualmachines\": { \"SingularDisplayName\": \"SCVMM virtual machine - Azure Arc\" }\r\n ,\"microsoft.scvmm/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine template\" }\r\n ,\"microsoft.scvmm/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual network\" }\r\n ,\"microsoft.scvmm/vmmservers\": { \"SingularDisplayName\": \"SCVMM management server\" }\r\n ,\"microsoft.search/searchservices\": { \"SingularDisplayName\": \"Search service\" }\r\n ,\"microsoft.secretmanagementsampleprovider/forecasts\": { \"SingularDisplayName\": \"Microsoft.SecretManagementSampleProvider forecast\" }\r\n ,\"microsoft.secretsynccontroller/azurekeyvaultsecretproviderclasses\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController Azure key vault secret provider class\" }\r\n ,\"microsoft.secretsynccontroller/secretsyncs\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController secret sync\" }\r\n ,\"microsoft.security/adaptivenetworkhardenings\": { \"SingularDisplayName\": \"Microsoft.Security adaptive network hardening\" }\r\n ,\"microsoft.security/advancedthreatprotectionsettings\": { \"SingularDisplayName\": \"Microsoft.Security advanced threat protection setting\" }\r\n ,\"microsoft.security/alertssuppressionrules\": { \"SingularDisplayName\": \"Microsoft.Security alerts suppression rule\" }\r\n ,\"microsoft.security/apicollections\": { \"SingularDisplayName\": \"Microsoft.Security API collection\" }\r\n ,\"microsoft.security/applications\": { \"SingularDisplayName\": \"Microsoft.Security application\" }\r\n ,\"microsoft.security/assessmentmetadata\": { \"SingularDisplayName\": \"Microsoft.Security assessment metadata\" }\r\n ,\"microsoft.security/assessments\": { \"SingularDisplayName\": \"Microsoft.Security assessment\" }\r\n ,\"microsoft.security/assessments/governanceassignments\": { \"SingularDisplayName\": \"Microsoft.Security assessments governance assignment\" }\r\n ,\"microsoft.security/assessments/subassessments\": { \"SingularDisplayName\": \"Microsoft.Security assessments sub assessment\" }\r\n ,\"microsoft.security/assignments\": { \"SingularDisplayName\": \"Microsoft.Security assignment\" }\r\n ,\"microsoft.security/automations\": { \"SingularDisplayName\": \"Microsoft.Security automation\" }\r\n ,\"microsoft.security/autoprovisioningsettings\": { \"SingularDisplayName\": \"Microsoft.Security auto provisioning setting\" }\r\n ,\"microsoft.security/complianceresults\": { \"SingularDisplayName\": \"Microsoft.Security compliance result\" }\r\n ,\"microsoft.security/compliances\": { \"SingularDisplayName\": \"Microsoft.Security compliance\" }\r\n ,\"microsoft.security/connectors\": { \"SingularDisplayName\": \"Microsoft.Security connector\" }\r\n ,\"microsoft.security/customassessmentautomations\": { \"SingularDisplayName\": \"Microsoft.Security custom assessment automation\" }\r\n ,\"microsoft.security/defenderforstoragesettings\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage setting\" }\r\n ,\"microsoft.security/defenderforstoragesettings/malwarescans\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage settings malware scan\" }\r\n ,\"microsoft.security/devicesecuritygroups\": { \"SingularDisplayName\": \"Microsoft.Security device security group\" }\r\n ,\"microsoft.security/governancerules\": { \"SingularDisplayName\": \"Microsoft.Security governance rule\" }\r\n ,\"microsoft.security/governancerules/operationresults\": { \"SingularDisplayName\": \"Microsoft.Security governance rules operation result\" }\r\n ,\"microsoft.security/healthreports\": { \"SingularDisplayName\": \"Microsoft.Security health report\" }\r\n ,\"microsoft.security/informationprotectionpolicies\": { \"SingularDisplayName\": \"Microsoft.Security information protection policy\" }\r\n ,\"microsoft.security/iotsecuritysolutions\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solution\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics model\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated alert\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated recommendation\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotalerttypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert type\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendationtypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation type\" }\r\n ,\"microsoft.security/locations/alerts\": { \"SingularDisplayName\": \"Security Alert\" }\r\n ,\"microsoft.security/mdeonboardings\": { \"SingularDisplayName\": \"Microsoft.Security mde onboarding\" }\r\n ,\"microsoft.security/pricings\": { \"SingularDisplayName\": \"Defender for Cloud\" }\r\n ,\"microsoft.security/pricings/securityoperators\": { \"SingularDisplayName\": \"Microsoft.Security pricings security operator\" }\r\n ,\"microsoft.security/regulatorycompliancestandards\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standard\" }\r\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance control\" }\r\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols/regulatorycomplianceassessments\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance controls regulatory compliance assessment\" }\r\n ,\"microsoft.security/securescores\": { \"SingularDisplayName\": \"Microsoft.Security secure score\" }\r\n ,\"microsoft.security/securityconnectors\": { \"SingularDisplayName\": \"Microsoft.Security security connector\" }\r\n ,\"microsoft.security/securityconnectors/devops\": { \"SingularDisplayName\": \"Microsoft.Security security connectors devop\" }\r\n ,\"microsoft.security/securitycontacts\": { \"SingularDisplayName\": \"Microsoft.Security security contact\" }\r\n ,\"microsoft.security/sensitivitysettings\": { \"SingularDisplayName\": \"Microsoft.Security sensitivity setting\" }\r\n ,\"microsoft.security/servervulnerabilityassessments\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessment\" }\r\n ,\"microsoft.security/servervulnerabilityassessmentssettings\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessments setting\" }\r\n ,\"microsoft.security/settings\": { \"SingularDisplayName\": \"Microsoft.Security setting\" }\r\n ,\"microsoft.security/standards\": { \"SingularDisplayName\": \"Microsoft.Security standard\" }\r\n ,\"microsoft.security/workspacesettings\": { \"SingularDisplayName\": \"Microsoft.Security workspace setting\" }\r\n ,\"microsoft.securitycopilot/capacities\": { \"SingularDisplayName\": \"Microsoft Security compute capacity\" }\r\n ,\"microsoft.securitydetonation/chambers\": { \"SingularDisplayName\": \"Security Detonation Chamber\" }\r\n ,\"microsoft.securityinsightsarg/sentinel\": { \"SingularDisplayName\": \"Microsoft Sentinel\" }\r\n ,\"microsoft.sentinelplatformservices/sentinelplatformservices\": { \"SingularDisplayName\": \"Microsoft.SentinelPlatformServices sentinel platform service\" }\r\n ,\"microsoft.serialconsole/consoleservices\": { \"SingularDisplayName\": \"Microsoft.SerialConsole console service\" }\r\n ,\"microsoft.serialconsole/serialports\": { \"SingularDisplayName\": \"Microsoft.SerialConsole serial port\" }\r\n ,\"microsoft.servicebus/namespaces\": { \"SingularDisplayName\": \"Service Bus namespace\" }\r\n ,\"microsoft.servicebus/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Service Bus Geo-DR Alias\" }\r\n ,\"microsoft.servicebus/namespaces/queues\": { \"SingularDisplayName\": \"Service Bus queue\" }\r\n ,\"microsoft.servicebus/namespaces/topics\": { \"SingularDisplayName\": \"Service Bus topic\" }\r\n ,\"microsoft.servicebus/namespaces/topics/subscriptions\": { \"SingularDisplayName\": \"Service Bus Subscription\" }\r\n ,\"microsoft.servicefabric/clusters\": { \"SingularDisplayName\": \"Service Fabric cluster\" }\r\n ,\"microsoft.servicefabric/managedclusters\": { \"SingularDisplayName\": \"Service Fabric managed cluster\" }\r\n ,\"microsoft.servicefabricmesh/applications\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh application\" }\r\n ,\"microsoft.servicefabricmesh/applications/services\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications service\" }\r\n ,\"microsoft.servicefabricmesh/applications/services/replicas\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications services replica\" }\r\n ,\"microsoft.servicefabricmesh/gateways\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh gateway\" }\r\n ,\"microsoft.servicefabricmesh/networks\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh network\" }\r\n ,\"microsoft.servicefabricmesh/secrets\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secret\" }\r\n ,\"microsoft.servicefabricmesh/secrets/values\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secrets value\" }\r\n ,\"microsoft.servicefabricmesh/volumes\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh volume\" }\r\n ,\"microsoft.servicelinker/dryruns\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker dryrun\" }\r\n ,\"microsoft.servicelinker/linkers\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker linker\" }\r\n ,\"microsoft.servicenetworking/trafficcontrollers\": { \"SingularDisplayName\": \"Application Gateway for Containers\" }\r\n ,\"microsoft.serviceshub/connectors\": { \"SingularDisplayName\": \"Services Hub Connector\" }\r\n ,\"microsoft.signalrservice/signalr\": { \"SingularDisplayName\": \"SignalR\" }\r\n ,\"microsoft.signalrservice/signalr/replicas\": { \"SingularDisplayName\": \"SignalR Replica\" }\r\n ,\"microsoft.signalrservice/webpubsub\": { \"SingularDisplayName\": \"Web PubSub Service\" }\r\n ,\"microsoft.signalrservice/webpubsub/replicas\": { \"SingularDisplayName\": \"Web PubSub Service Replica\" }\r\n ,\"microsoft.skytap/billingnodes\": { \"SingularDisplayName\": \"Microsoft.Skytap billing node\" }\r\n ,\"microsoft.skytap/interfaces\": { \"SingularDisplayName\": \"Microsoft.Skytap interface\" }\r\n ,\"microsoft.skytap/nodes\": { \"SingularDisplayName\": \"Microsoft.Skytap node\" }\r\n ,\"microsoft.softwareplan/hybridusebenefits\": { \"SingularDisplayName\": \"Microsoft.SoftwarePlan hybrid use benefit\" }\r\n ,\"microsoft.solutions/applicationdefinitions\": { \"SingularDisplayName\": \"Service catalog managed application definition\" }\r\n ,\"microsoft.solutions/applications\": { \"SingularDisplayName\": \"Managed application\" }\r\n ,\"microsoft.solutions/jitrequests\": { \"SingularDisplayName\": \"Microsoft.Solutions JIT request\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts\": { \"SingularDisplayName\": \"Landing zone account\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\r\n ,\"microsoft.sovereign/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\r\n ,\"microsoft.sovereign/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\r\n ,\"microsoft.sovereign/transparencylogs\": { \"SingularDisplayName\": \"Transparency log\" }\r\n ,\"microsoft.sql/azuresql\": { \"SingularDisplayName\": \"Azure SQL resource\" }\r\n ,\"microsoft.sql/instancepools\": { \"SingularDisplayName\": \"Instance pool\" }\r\n ,\"microsoft.sql/managedinstances\": { \"SingularDisplayName\": \"SQL managed instance\" }\r\n ,\"microsoft.sql/managedinstances/databases\": { \"SingularDisplayName\": \"Managed database\" }\r\n ,\"microsoft.sql/servers\": { \"SingularDisplayName\": \"SQL server\" }\r\n ,\"microsoft.sql/servers/databases\": { \"SingularDisplayName\": \"SQL database\" }\r\n ,\"microsoft.sql/servers/elasticpools\": { \"SingularDisplayName\": \"SQL elastic pool\" }\r\n ,\"microsoft.sql/servers/jobagents\": { \"SingularDisplayName\": \"Elastic Job agent\" }\r\n ,\"microsoft.sql/virtualclusters\": { \"SingularDisplayName\": \"Virtual cluster\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine group\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups/availabilitygrouplisteners\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine groups availability group listener\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachines\": { \"SingularDisplayName\": \"SQL virtual machine\" }\r\n ,\"microsoft.standbypool/standbycontainergrouppools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pool\" }\r\n ,\"microsoft.standbypool/standbycontainergrouppools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pools runtime view\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pool\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools runtime view\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools/standbyvirtualmachines\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools standby virtual machine\" }\r\n ,\"microsoft.storage/storageaccounts\": { \"SingularDisplayName\": \"Storage account\" }\r\n ,\"microsoft.storageactions/storagetasks\": { \"SingularDisplayName\": \"Storage task - Azure Storage Actions\" }\r\n ,\"microsoft.storagecache/amlfilesystems\": { \"SingularDisplayName\": \"Azure Managed Lustre\" }\r\n ,\"microsoft.storagecache/caches\": { \"SingularDisplayName\": \"HPC cache\" }\r\n ,\"microsoft.storagediscovery/storagediscoveryworkspaces\": { \"SingularDisplayName\": \"Storage Discovery workspace\" }\r\n ,\"microsoft.storagehub/all\": { \"SingularDisplayName\": \"All resources\" }\r\n ,\"microsoft.storagehub/policycomplianceresources\": { \"SingularDisplayName\": \"Policy compliance\" }\r\n ,\"microsoft.storageinsights/storagecollectionrules\": { \"SingularDisplayName\": \"Microsoft.StorageInsights storage collection rule\" }\r\n ,\"microsoft.storagemover/storagemovers\": { \"SingularDisplayName\": \"Storage mover\" }\r\n ,\"microsoft.storagepool/diskpools\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pool\" }\r\n ,\"microsoft.storagepool/diskpools/iscsitargets\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pools iscsi target\" }\r\n ,\"microsoft.storagesync/storagesyncservices\": { \"SingularDisplayName\": \"Storage Sync Service\" }\r\n ,\"microsoft.storagetasks/storagetasks\": { \"SingularDisplayName\": \"Microsoft.StorageTasks storage task\" }\r\n ,\"microsoft.storsimple/managers\": { \"SingularDisplayName\": \"StorSimple device manager\" }\r\n ,\"microsoft.storsimple/managers/accesscontrolrecords\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers access control record\" }\r\n ,\"microsoft.storsimple/managers/bandwidthsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers bandwidth setting\" }\r\n ,\"microsoft.storsimple/managers/certificates\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers certificate\" }\r\n ,\"microsoft.storsimple/managers/devices\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers device\" }\r\n ,\"microsoft.storsimple/managers/devices/alertsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices alert setting\" }\r\n ,\"microsoft.storsimple/managers/devices/backuppolicies\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policy\" }\r\n ,\"microsoft.storsimple/managers/devices/backuppolicies/schedules\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policies schedule\" }\r\n ,\"microsoft.storsimple/managers/devices/backupschedulegroups\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup schedule group\" }\r\n ,\"microsoft.storsimple/managers/devices/chapsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices chap setting\" }\r\n ,\"microsoft.storsimple/managers/devices/fileservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileserver\" }\r\n ,\"microsoft.storsimple/managers/devices/fileservers/shares\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileservers share\" }\r\n ,\"microsoft.storsimple/managers/devices/iscsiservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiserver\" }\r\n ,\"microsoft.storsimple/managers/devices/iscsiservers/disks\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiservers disk\" }\r\n ,\"microsoft.storsimple/managers/devices/jobs\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices job\" }\r\n ,\"microsoft.storsimple/managers/devices/networksettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices network setting\" }\r\n ,\"microsoft.storsimple/managers/devices/securitysettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices security setting\" }\r\n ,\"microsoft.storsimple/managers/devices/timesettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices time setting\" }\r\n ,\"microsoft.storsimple/managers/devices/updatesummary\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices update summary\" }\r\n ,\"microsoft.storsimple/managers/devices/volumecontainers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume container\" }\r\n ,\"microsoft.storsimple/managers/devices/volumecontainers/volumes\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume containers volume\" }\r\n ,\"microsoft.storsimple/managers/encryptionsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers encryption setting\" }\r\n ,\"microsoft.storsimple/managers/extendedinformation\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers extended information\" }\r\n ,\"microsoft.storsimple/managers/storageaccountcredentials\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage account credential\" }\r\n ,\"microsoft.storsimple/managers/storagedomains\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage domain\" }\r\n ,\"microsoft.streamanalytics/clusters\": { \"SingularDisplayName\": \"Stream Analytics cluster\" }\r\n ,\"microsoft.streamanalytics/streamingjobs\": { \"SingularDisplayName\": \"Stream Analytics job\" }\r\n ,\"microsoft.subscription/aliases\": { \"SingularDisplayName\": \"Microsoft.Subscription aliase\" }\r\n ,\"microsoft.subscription/changetenantrequest\": { \"SingularDisplayName\": \"Microsoft.Subscription change tenant request\" }\r\n ,\"microsoft.subscription/policies\": { \"SingularDisplayName\": \"Microsoft.Subscription policy\" }\r\n ,\"microsoft.subscription/subscriptiondefinitions\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription definition\" }\r\n ,\"microsoft.subscription/subscriptionoperations\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription operation\" }\r\n ,\"microsoft.support/fileworkspaces\": { \"SingularDisplayName\": \"Microsoft.Support file workspace\" }\r\n ,\"microsoft.support/fileworkspaces/files\": { \"SingularDisplayName\": \"Microsoft.Support file workspaces file\" }\r\n ,\"microsoft.support/services\": { \"SingularDisplayName\": \"Microsoft.Support service\" }\r\n ,\"microsoft.support/services/problemclassifications\": { \"SingularDisplayName\": \"Microsoft.Support services problem classification\" }\r\n ,\"microsoft.support/supporttickets\": { \"SingularDisplayName\": \"Support Request\" }\r\n ,\"microsoft.sustainabilityservices/calculations\": { \"SingularDisplayName\": \"Project Sustainability Calculator\" }\r\n ,\"microsoft.symphony/instances\": { \"SingularDisplayName\": \"Microsoft.Symphony instance\" }\r\n ,\"microsoft.symphony/solutions\": { \"SingularDisplayName\": \"Microsoft.Symphony solution\" }\r\n ,\"microsoft.symphony/targets\": { \"SingularDisplayName\": \"Microsoft.Symphony target\" }\r\n ,\"microsoft.synapse/privatelinkhubs\": { \"SingularDisplayName\": \"Synapse private link hub\" }\r\n ,\"microsoft.synapse/workspaces\": { \"SingularDisplayName\": \"Synapse workspace\" }\r\n ,\"microsoft.synapse/workspaces/bigdatapools\": { \"SingularDisplayName\": \"Apache Spark pool\" }\r\n ,\"microsoft.synapse/workspaces/kustopools\": { \"SingularDisplayName\": \"Data Explorer pool\" }\r\n ,\"microsoft.synapse/workspaces/kustopools/databases\": { \"SingularDisplayName\": \"Data Explorer Database\" }\r\n ,\"microsoft.synapse/workspaces/scopepools\": { \"SingularDisplayName\": \"SCOPE pool\" }\r\n ,\"microsoft.synapse/workspaces/sqlpools\": { \"SingularDisplayName\": \"Dedicated SQL pool\" }\r\n ,\"microsoft.syntex/accounts\": { \"SingularDisplayName\": \"Microsoft.Syntex account\" }\r\n ,\"microsoft.syntex/documentprocessors\": { \"SingularDisplayName\": \"Microsoft.Syntex document processor\" }\r\n ,\"microsoft.test/healthdataaiservices\": { \"SingularDisplayName\": \"Azure Health Data and AI Services\" }\r\n ,\"microsoft.timeseriesinsights/environments\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environment\" }\r\n ,\"microsoft.timeseriesinsights/environments/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments access policy\" }\r\n ,\"microsoft.timeseriesinsights/environments/eventsources\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments event source\" }\r\n ,\"microsoft.timeseriesinsights/environments/referencedatasets\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments reference data set\" }\r\n ,\"microsoft.toolchainorchestrator/activations\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator activation\" }\r\n ,\"microsoft.toolchainorchestrator/campaigns\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaign\" }\r\n ,\"microsoft.toolchainorchestrator/campaigns/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaigns version\" }\r\n ,\"microsoft.toolchainorchestrator/catalogs\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalog\" }\r\n ,\"microsoft.toolchainorchestrator/catalogs/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalogs version\" }\r\n ,\"microsoft.toolchainorchestrator/diagnostics\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator diagnostic\" }\r\n ,\"microsoft.toolchainorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instance\" }\r\n ,\"microsoft.toolchainorchestrator/instances/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instances version\" }\r\n ,\"microsoft.toolchainorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solution\" }\r\n ,\"microsoft.toolchainorchestrator/solutions/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solutions version\" }\r\n ,\"microsoft.toolchainorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator target\" }\r\n ,\"microsoft.toolchainorchestrator/targets/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator targets version\" }\r\n ,\"microsoft.updatemanager/updaterules\": { \"SingularDisplayName\": \"Update Rule\" }\r\n ,\"microsoft.usagebilling/accounts\": { \"SingularDisplayName\": \"Microsoft.UsageBilling account\" }\r\n ,\"microsoft.usagebilling/accounts/dataexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts data export\" }\r\n ,\"microsoft.usagebilling/accounts/inputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts input\" }\r\n ,\"microsoft.usagebilling/accounts/metricexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts metric export\" }\r\n ,\"microsoft.usagebilling/accounts/pav2outputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pav2output\" }\r\n ,\"microsoft.usagebilling/accounts/pipelines\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipeline\" }\r\n ,\"microsoft.usagebilling/accounts/pipelines/outputselectors\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipelines output selector\" }\r\n ,\"microsoft.verifiedid/authorities\": { \"SingularDisplayName\": \"Microsoft.VerifiedId authority\" }\r\n ,\"microsoft.videoindexer/accounts\": { \"SingularDisplayName\": \"Azure AI Video Indexer\" }\r\n ,\"microsoft.virtualmachineimages/imagetemplates\": { \"SingularDisplayName\": \"Image template\" }\r\n ,\"microsoft.visualstudio/account\": { \"SingularDisplayName\": \"Azure DevOps organization\" }\r\n ,\"microsoft.vmware/resourcepools\": { \"SingularDisplayName\": \"Microsoft.VMware resource pool\" }\r\n ,\"microsoft.vmware/vcenters\": { \"SingularDisplayName\": \"Microsoft.VMware vcenter\" }\r\n ,\"microsoft.vmware/vcenters/inventoryitems\": { \"SingularDisplayName\": \"Microsoft.VMware vcenters inventory item\" }\r\n ,\"microsoft.vmware/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine\" }\r\n ,\"microsoft.vmware/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine template\" }\r\n ,\"microsoft.vmware/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.VMware virtual network\" }\r\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudnodes\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud node\" }\r\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudservices\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud service\" }\r\n ,\"microsoft.vmwarecloudsimple/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple virtual machine\" }\r\n ,\"microsoft.vnfmanager/devices\": { \"SingularDisplayName\": \"Microsoft.VnfManager device\" }\r\n ,\"microsoft.vnfmanager/vendors\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendor\" }\r\n ,\"microsoft.vnfmanager/vendors/skus\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendors SKU\" }\r\n ,\"microsoft.vnfmanager/vnfs\": { \"SingularDisplayName\": \"Microsoft.VnfManager vnf\" }\r\n ,\"microsoft.voiceservices/communicationsgateways\": { \"SingularDisplayName\": \"Communications Gateway\" }\r\n ,\"microsoft.voiceservices/communicationsgateways/testlines\": { \"SingularDisplayName\": \"Communications Gateway Test Line\" }\r\n ,\"microsoft.vsonline/accounts\": { \"SingularDisplayName\": \"Microsoft.VSOnline account\" }\r\n ,\"microsoft.vsonline/plans\": { \"SingularDisplayName\": \"Visual Studio Online Plan\" }\r\n ,\"microsoft.web/certificates\": { \"SingularDisplayName\": \"Microsoft.Web certificate\" }\r\n ,\"microsoft.web/connectiongateways\": { \"SingularDisplayName\": \"App Service on-premises data gateway\" }\r\n ,\"microsoft.web/connections\": { \"SingularDisplayName\": \"App Service API connection\" }\r\n ,\"microsoft.web/containerapps\": { \"SingularDisplayName\": \"Microsoft.Web container app\" }\r\n ,\"microsoft.web/containerapps/revisions\": { \"SingularDisplayName\": \"Microsoft.Web container apps revision\" }\r\n ,\"microsoft.web/customapis\": { \"SingularDisplayName\": \"Logic apps custom connector\" }\r\n ,\"microsoft.web/deletedsites\": { \"SingularDisplayName\": \"Microsoft.Web deleted site\" }\r\n ,\"microsoft.web/hostingenvironments\": { \"SingularDisplayName\": \"App Service Environment\" }\r\n ,\"microsoft.web/ishostingenvironmentnameavailable\": { \"SingularDisplayName\": \"Microsoft.Web ishostingenvironmentnameavailable\" }\r\n ,\"microsoft.web/kubeenvironments\": { \"SingularDisplayName\": \"App Service Kubernetes Environment\" }\r\n ,\"microsoft.web/logicappstemplate\": { \"SingularDisplayName\": \"Logic Apps Template\" }\r\n ,\"microsoft.web/publishingusers\": { \"SingularDisplayName\": \"Microsoft.Web publishing user\" }\r\n ,\"microsoft.web/serverfarms\": { \"SingularDisplayName\": \"App Service plan\" }\r\n ,\"microsoft.web/sites\": { \"SingularDisplayName\": \"App Service web app\" }\r\n ,\"microsoft.web/sites/slots\": { \"SingularDisplayName\": \"App Service deployment slot\" }\r\n ,\"microsoft.web/sourcecontrols\": { \"SingularDisplayName\": \"Microsoft.Web sourcecontrol\" }\r\n ,\"microsoft.web/staticsites\": { \"SingularDisplayName\": \"Static Web App\" }\r\n ,\"microsoft.weightsandbiases/instances\": { \"SingularDisplayName\": \"Azure Native Weights & Biases Cloud Service\" }\r\n ,\"microsoft.whiteboxcadlprovider/whiteboxresources\": { \"SingularDisplayName\": \"Microsoft.WhiteBoxCadlProvider white box resource\" }\r\n ,\"microsoft.windows365/cloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.Windows365 cloud pc delegated msi\" }\r\n ,\"microsoft.windowsesu/multipleactivationkeys\": { \"SingularDisplayName\": \"Microsoft.WindowsESU multiple activation key\" }\r\n ,\"microsoft.windowsiot/deviceservices\": { \"SingularDisplayName\": \"Microsoft.WindowsIoT device service\" }\r\n ,\"microsoft.windowspushnotificationservices/registrations\": { \"SingularDisplayName\": \"Windows Push Notification Service\" }\r\n ,\"microsoft.workloadmonitor/monitors\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitor\" }\r\n ,\"microsoft.workloadmonitor/monitors/history\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitors history\" }\r\n ,\"microsoft.workloads/configurationvalidationresults\": { \"SingularDisplayName\": \"Microsoft.Workloads configuration validation result\" }\r\n ,\"microsoft.workloads/connectors\": { \"SingularDisplayName\": \"Microsoft.Workloads connector\" }\r\n ,\"microsoft.workloads/connectors/acssbackups\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors acss backup\" }\r\n ,\"microsoft.workloads/connectors/amsinsights\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors ams insight\" }\r\n ,\"microsoft.workloads/connectors/sapvirtualinstancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors sap virtual instance monitor\" }\r\n ,\"microsoft.workloads/epicvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for Epic solution\" }\r\n ,\"microsoft.workloads/insights\": { \"SingularDisplayName\": \"Microsoft.Workloads insight\" }\r\n ,\"microsoft.workloads/instancegroupmonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance group monitor\" }\r\n ,\"microsoft.workloads/instancehealthdefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definition\" }\r\n ,\"microsoft.workloads/instancehealthdefinitions/signaldefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definitions signal definition\" }\r\n ,\"microsoft.workloads/instancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance monitor\" }\r\n ,\"microsoft.workloads/monitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP solutions\" }\r\n ,\"microsoft.workloads/oraclevirtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instance\" }\r\n ,\"microsoft.workloads/oraclevirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instances database instance\" }\r\n ,\"microsoft.workloads/phpworkloads\": { \"SingularDisplayName\": \"Microsoft.Workloads php workload\" }\r\n ,\"microsoft.workloads/phpworkloads/wordpressinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads php workloads wordpress instance\" }\r\n ,\"microsoft.workloads/sapdiscoverysites\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery site\" }\r\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instance\" }\r\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances/serverinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instances server instance\" }\r\n ,\"microsoft.workloads/sapvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/applicationinstances\": { \"SingularDisplayName\": \"App server instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/centralinstances\": { \"SingularDisplayName\": \"Central service instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Database for SAP solutions\" }\r\n ,\"microsoft.workloads/virtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instance\" }\r\n ,\"microsoft.workloads/virtualinstances/components\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instances component\" }\r\n ,\"microsoft.workloads/workloadinstance\": { \"SingularDisplayName\": \"My Resource\" }\r\n ,\"microsoft.zerotrustsegmentation/segmentationmanagers\": { \"SingularDisplayName\": \"Segmentation Manager\" }\r\n ,\"mongodb.atlas/organizations\": { \"SingularDisplayName\": \"MongoDB Atlas Organization\" }\r\n ,\"neon.postgres/organizations\": { \"SingularDisplayName\": \"Neon Serverless Postgres Organization\" }\r\n ,\"newrelic.observability/monitors\": { \"SingularDisplayName\": \"New Relic\" }\r\n ,\"nginx.nginxplus/nginxdeployments\": { \"SingularDisplayName\": \"NGINXaaS\" }\r\n ,\"oracle.database/autonomousdatabases\": { \"SingularDisplayName\": \"Autonomous Database\" }\r\n ,\"oracle.database/basedb\": { \"SingularDisplayName\": \"Autonomous Database\" }\r\n ,\"oracle.database/cloudexadatainfrastructures\": { \"SingularDisplayName\": \"Oracle Exadata Infrastructure\" }\r\n ,\"oracle.database/cloudvmclusters\": { \"SingularDisplayName\": \"Oracle Exadata VM Cluster\" }\r\n ,\"oracle.database/exadbvmclusters\": { \"SingularDisplayName\": \"Oracle Exascale VM Cluster\" }\r\n ,\"oracle.database/exascaledbstoragevaults\": { \"SingularDisplayName\": \"Oracle Exascale DB Storage Vault\" }\r\n ,\"oracle.database/networkanchors\": { \"SingularDisplayName\": \"Network Anchor\" }\r\n ,\"oracle.database/oraclesubscriptions\": { \"SingularDisplayName\": \"OracleSubscription\" }\r\n ,\"oracle.database/resourceanchors\": { \"SingularDisplayName\": \"Resource Anchor\" }\r\n ,\"paloaltonetworks.cloudngfw/firewalls\": { \"SingularDisplayName\": \"Cloud NGFW by Palo Alto Networks\" }\r\n ,\"paloaltonetworks.cloudngfw/globalrulestacks\": { \"SingularDisplayName\": \"Global Rulestack\" }\r\n ,\"paloaltonetworks.cloudngfw/localrulestacks\": { \"SingularDisplayName\": \"Local Rulestack for Cloud NGFW by Palo Alto Networks\" }\r\n ,\"pinecone.vectordb/organizations\": { \"SingularDisplayName\": \"Azure Native Pinecone Cloud Service\" }\r\n ,\"purestorage.block/reservations\": { \"SingularDisplayName\": \"Azure Native Pure Storage Cloud Service\" }\r\n ,\"purestorage.block/storagepools\": { \"SingularDisplayName\": \"Storage pool\" }\r\n ,\"purestorage.block/storagepools/avsstoragecontainers\": { \"SingularDisplayName\": \"PureStorage.Block storage pools avs storage container\" }\r\n })[tolower(id)]\r\n}\r\n", - "$fxv#4": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_5(id: string) {\r\n dynamic({\r\n \"qumulo.qaas/storages\": { \"SingularDisplayName\": \"Qumulo.QaaS storage\" }\r\n ,\"qumulo.storage/filesystems\": { \"SingularDisplayName\": \"Azure Native Qumulo Scalable File Service\" }\r\n ,\"solarwinds.observability/organizations\": { \"SingularDisplayName\": \"SolarWinds Observability\" }\r\n ,\"splitio.experimentation/experimentationworkspaces\": { \"SingularDisplayName\": \"Split Experimentation Workspace\" }\r\n ,\"wandisco.fusion/migrators\": { \"SingularDisplayName\": \"LiveData Migrator\" }\r\n ,\"wandisco.fusion/migrators/datatransferagents\": { \"SingularDisplayName\": \"Data Transfer Agent\" }\r\n ,\"wandisco.fusion/migrators/exclusiontemplates\": { \"SingularDisplayName\": \"Exclusion\" }\r\n ,\"wandisco.fusion/migrators/livedatamigrations\": { \"SingularDisplayName\": \"Migration\" }\r\n ,\"wandisco.fusion/migrators/metadatamigrations\": { \"SingularDisplayName\": \"Metadata Migration\" }\r\n ,\"wandisco.fusion/migrators/metadatatargets\": { \"SingularDisplayName\": \"Metadata Target\" }\r\n ,\"wandisco.fusion/migrators/pathmappings\": { \"SingularDisplayName\": \"Path Mapping\" }\r\n ,\"wandisco.fusion/migrators/targets\": { \"SingularDisplayName\": \"Target\" }\r\n ,\"wandisco.fusion/migrators/verifications\": { \"SingularDisplayName\": \"Verification\" }\r\n })[tolower(id)]\r\n}\r\n", - "$fxv#5": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n// resource_type\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData')\r\nresource_type(id: string) {\r\n coalesce(_resource_type_1(id), _resource_type_2(id), _resource_type_3(id), _resource_type_4(id), _resource_type_5(id))\r\n}\r\n", - "$fxv#6": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Common utility functions\r\n//\r\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\r\n//======================================================================================================================\r\n\r\n\r\n//===| Date functions |=================================================================================================\r\n\r\n// monthstring\r\n.create-or-alter function \r\nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \r\nmonthstring(['date']: datetime, length: int = 9)\r\n{\r\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\r\n}\r\n\r\n// datestring\r\n.create-or-alter function \r\nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \r\ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n let month = (d: datetime) { monthstring(d, 3) };\r\n let endDate = iff(end == datetime('0001-01-01'), start, end);\r\n let sameDate = startofday(start) == startofday(endDate);\r\n let sameMonth = startofmonth(start) == startofmonth(endDate);\r\n let sameYear = startofyear(start) == startofyear(endDate);\r\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\r\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\r\n let currentYear = sameYear and startofyear(start) == startofyear(now());\r\n case(\r\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\r\n fullYear,\r\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\r\n // 1 full mo, same year | Mmm yyyy\r\n fullMonth and sameMonth and sameYear,\r\n strcat(month(start), ' ', getyear(start)),\r\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\r\n fullMonth and sameYear,\r\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\r\n fullMonth and not(sameYear),\r\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\r\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\r\n sameDate,\r\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\r\n not(fullMonth) and sameMonth and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\r\n not(fullMonth) and not(sameMonth) and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\r\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\r\n )\r\n}\r\n\r\n// daterange\r\n.create-or-alter function \r\nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \r\ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n datestring(start, end)\r\n}\r\n\r\n// monthsago\r\n.create-or-alter function \r\nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\r\nmonthsago(months: int)\r\n{\r\n datetime_add('month', -months, startofmonth(now()))\r\n}\r\n\r\n\r\n//===| Number functions |===============================================================================================\r\n// NOTE: Must be defined before string converters\r\n\r\n// delta\r\n.create-or-alter function \r\nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \r\ndelta(oldval: double, newval: double)\r\n{\r\n (newval - todouble(oldval))/oldval\r\n}\r\n\r\n// percentOfTotal\r\n// NOTE: Must be before percent() function\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercentOfTotal(t: (Count: long), tot: long)\r\n{\r\n let total = todouble(tot);\r\n t \r\n | extend Percent = round(Count / total * 100, 3) \r\n | order by Count desc\r\n}\r\n\r\n// percent\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercent(t: (Count: long))\r\n{\r\n let total = todouble(toscalar(t | summarize sum(Count)));\r\n percentOfTotal(t, total)\r\n}\r\n\r\n// plusminus\r\n.create-or-alter function \r\nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\r\nplusminus(val: string)\r\n{\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, val, strcat('+', val))\r\n}\r\n\r\n// updown\r\n.create-or-alter function \r\nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\r\nupdown(val: string)\r\n{\r\n // TODO: Handle 0\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\r\n}\r\n\r\n\r\n//===| String functions |===============================================================================================\r\n\r\n// percentstring\r\n// NOTE: Must be defined before deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\r\npercentstring(num: double, total: double = 1.0, places: int = 9)\r\n{\r\n let value = 1.0 * num / total * 100;\r\n strcat(case(\r\n places != 9, round(value, places),\r\n value < 10, round(value, 2),\r\n round(value, 1)\r\n ), '%')\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// arraystring\r\n.create-or-alter function \r\nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\r\narraystring(arr: dynamic)\r\n{\r\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\r\n tostring(arr)\r\n , @'^\\[\"', '')\r\n , @'\"\\]$', '')\r\n , @'^, ', '')\r\n , @', $', '')\r\n , @'^\\[]$', '')\r\n , '\",\"', ', ')\r\n}\r\n\r\n// deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\r\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\r\n{\r\n let d = delta(oldval, newval);\r\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\r\n}\r\n\r\n// diffstring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\r\ndiffstring(oldval: double, newval: double, places: int = 1)\r\n{\r\n plusminus(round(newval - oldval, places))\r\n}\r\n\r\n// numberstring\r\n.create-or-alter function \r\nwith (docstring = 'Convert a number to a string', folder = 'Common')\r\nnumberstring(num: double, abbrev: bool = true)\r\n{\r\n replace_regex(case(\r\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\r\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\r\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\r\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\r\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\r\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\r\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\r\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\r\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\r\n tostring(num)\r\n ), @'\\.0$', '')\r\n}\r\n\r\n\r\n//===| Other |==========================================================================================================\r\n\r\n// ifempty\r\n.create-or-alter function \r\nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\r\nifempty(val: dynamic, defaultVal: dynamic)\r\n{\r\n iff(isempty(val), defaultVal, val)\r\n}\r\n", - "$fxv#7": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Settings |=======================================================================================================\r\n\r\n.create-merge table HubSettingsLog (\r\n version: string,\r\n scopes: dynamic,\r\n retention: dynamic\r\n)\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// HubSettings function\r\n.create-or-alter function\r\nwith (docstring='Gets the latest version of hub settings.', folder='Settings')\r\nHubSettings()\r\n{\r\n HubSettingsLog\r\n | extend timestamp = ingestion_time()\r\n | summarize arg_max(timestamp, *)\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// HubScopes function\r\n.create-or-alter function\r\nwith (docstring='Gets the currently configured scopes.', folder='Settings')\r\nHubScopes()\r\n{\r\n HubSettings\r\n | project scopes\r\n | mv-expand scopes\r\n}\r\n\r\n\r\n//===| Open data |======================================================================================================\r\n\r\n// PricingUnits -- Create table if it doesn't exist\r\n.create-merge table PricingUnits ( ignore: string )\r\n\r\n// PricingUnits -- Remove all columns\r\n.alter table PricingUnits ( ignore: string )\r\n\r\n// PricingUnits -- Redefine all columns to change types\r\n.alter table PricingUnits (\r\n x_PricingUnitDescription: string,\r\n x_PricingBlockSize: real,\r\n PricingUnit: string\r\n)\r\n\r\n// Regions\r\n.create-merge table Regions(\r\n ResourceLocation: string,\r\n RegionId: string,\r\n RegionName: string\r\n)\r\n\r\n// ResourceTypes\r\n.create-merge table ResourceTypes(\r\n x_ResourceType: string,\r\n SingularDisplayName: string,\r\n PluralDisplayName: string,\r\n LowerSingularDisplayName: string,\r\n LowerPluralDisplayName: string,\r\n IsPreview: bool,\r\n Description: string,\r\n IconUri: string\r\n)\r\n\r\n// Services\r\n.create-merge table Services(\r\n x_ConsumedService: string,\r\n x_ResourceType: string,\r\n ServiceName: string,\r\n ServiceCategory: string,\r\n ServiceSubcategory: string,\r\n PublisherName: string,\r\n x_PublisherCategory: string,\r\n x_Environment: string,\r\n x_ServiceModel: string\r\n)\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// parse_resourceid\r\n.create-or-alter function\r\nwith (docstring = 'Parses an Azure resource ID to extract resource attributes like the name, type, resource group, and subaccount ID.', folder = 'Common')\r\nparse_resourceid(resourceId: string) {\r\n let ResourceId = tolower(resourceId);\r\n // let ResourceId = tolower('/providers/Microsoft.BillingBenefits/savingsPlanOrders/2d2e284b-0638-427e-b8c6-1b874d4f17c8/sp/xxx');\r\n let SubAccountId = tostring(extract('/subscriptions/[^/]+', 1, ResourceId));\r\n let x_ResourceGroupName = tostring(extract('/resourcegroups/[^/]+', 1, ResourceId));\r\n let providerPath = iff(ResourceId !contains '/providers/', '', split(iff(ResourceId startswith '/subscriptions/', strcat('/providers/microsoft.resources/', ResourceId), ResourceId), '/providers/')[-1]);\r\n let x_ResourceProvider = iff(isempty(providerPath), '', split(providerPath, '/')[0]);\r\n let tmp_ResourceProviderPath = iff(isempty(providerPath), '', substring(providerPath, strlen(x_ResourceProvider) + 1));\r\n let segments = split(tmp_ResourceProviderPath, '/');\r\n let ResourceName = trim(@'/+', replace_string(strcat_array(array_iff(\r\n dynamic([false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true]),\r\n segments, dynamic([])), '/'), '//', '/'));\r\n let x_ResourceTypePath = trim(@'/+', replace_string(strcat_array(array_iff(\r\n dynamic([true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]),\r\n segments, dynamic([])), '/'), '//', '/'));\r\n let xRT = iff(isempty(x_ResourceProvider) or isempty(x_ResourceTypePath), '', strcat(x_ResourceProvider, '/', x_ResourceTypePath));\r\n // TODO: Remove ResourceType in 0.9\r\n bag_pack('ResourceId', ResourceId, 'ResourceName', ResourceName, 'ResourceType', xRT, 'SubAccountId', SubAccountId, 'x_ResourceGroupName', x_ResourceGroupName, 'x_ResourceProvider', x_ResourceProvider, 'x_ResourceType', xRT)\r\n}\r\n", - "$fxv#8": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| ActualCosts |====================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_raw table -- Create the table if it doesn't exist\r\n.create-merge table ActualCosts_raw ( ignore: string )\r\n\r\n// ActualCosts_raw table -- Remove all columns to allow changing column types\r\n.alter table ActualCosts_raw ( ignore: string )\r\n\r\n// ActualCosts_raw table -- Redefine all columns\r\n.alter table ActualCosts_raw (\r\n AccountName: string,\r\n AccountOwnerId: string,\r\n AdditionalInfo: string,\r\n AvailabilityZone: string,\r\n BillingAccountId: string, \r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n BillingPeriodEndDate: datetime,\r\n BillingPeriodStartDate: datetime,\r\n BillingProfileId: string,\r\n BillingProfileName: string,\r\n ChargeType: string,\r\n ConsumedService: string,\r\n CostCenter: string,\r\n Cost: real,\r\n Date: datetime,\r\n EffectivePrice: real,\r\n Frequency: string,\r\n InvoiceSection: string,\r\n InvoiceSectionId: string,\r\n IsAzureCreditEligible: bool,\r\n MeterCategory: string,\r\n MeterId: string,\r\n MeterName: string,\r\n MeterRegion: string,\r\n MeterSubCategory: string,\r\n OfferId: string,\r\n PartNumber: string,\r\n PlanName: string,\r\n Product: string,\r\n ProductOrderId: string,\r\n ProductOrderName: string,\r\n PublisherName: string,\r\n PublisherType: string,\r\n Quantity: real,\r\n ReservationId: string,\r\n ReservationName: string,\r\n ResourceGroup: string,\r\n ResourceId: string,\r\n ResourceLocation: string,\r\n ResourceName: string,\r\n ServiceFamily: string,\r\n ServiceInfo1: string,\r\n ServiceInfo2: string,\r\n SubscriptionId: string,\r\n SubscriptionName: string,\r\n Tags: string,\r\n Term: string,\r\n UnitOfMeasure: string,\r\n UnitPrice: real\r\n)\r\n\r\n// ActualCosts_raw ingestion mapping\r\n.create-or-alter table ActualCosts_raw ingestion parquet mapping \"ActualCosts_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\r\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\r\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\r\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\r\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\r\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\r\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\r\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\r\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\r\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\r\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\r\n]\r\n```\r\n\r\n// ActualCosts_raw retention policy (clear historical data)\r\n.alter-merge table ActualCosts_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// ActualCosts_raw retention policy (set the user-defined retention period)\r\n.alter-merge table ActualCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable ActualCosts_raw streaming ingestion (required for Fabric)\r\n.alter table ActualCosts_raw policy streamingingestion disable\r\n\r\n\r\n//===| AmortizedCosts |=================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_raw table -- Create the table if it doesn't exist\r\n.create-merge table AmortizedCosts_raw ( ignore: string )\r\n\r\n// AmortizedCosts_raw table -- Remove all columns to allow changing column types\r\n.alter table AmortizedCosts_raw ( ignore: string )\r\n\r\n// AmortizedCosts_raw table -- Redefine all columns\r\n.alter table AmortizedCosts_raw (\r\n AccountName: string,\r\n AccountOwnerId: string,\r\n AdditionalInfo: string,\r\n AvailabilityZone: string,\r\n BillingAccountId: string, \r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n BillingPeriodEndDate: datetime,\r\n BillingPeriodStartDate: datetime,\r\n BillingProfileId: string,\r\n BillingProfileName: string,\r\n ChargeType: string,\r\n ConsumedService: string,\r\n CostCenter: string,\r\n Cost: real,\r\n Date: datetime,\r\n EffectivePrice: real,\r\n Frequency: string,\r\n InvoiceSection: string,\r\n InvoiceSectionId: string,\r\n IsAzureCreditEligible: bool,\r\n MeterCategory: string,\r\n MeterId: string,\r\n MeterName: string,\r\n MeterRegion: string,\r\n MeterSubCategory: string,\r\n OfferId: string,\r\n PartNumber: string,\r\n PlanName: string,\r\n Product: string,\r\n ProductOrderId: string,\r\n ProductOrderName: string,\r\n PublisherName: string,\r\n PublisherType: string,\r\n Quantity: real,\r\n ReservationId: string,\r\n ReservationName: string,\r\n ResourceGroup: string,\r\n ResourceId: string,\r\n ResourceLocation: string,\r\n ResourceName: string,\r\n ServiceFamily: string,\r\n ServiceInfo1: string,\r\n ServiceInfo2: string,\r\n SubscriptionId: string,\r\n SubscriptionName: string,\r\n Tags: string,\r\n Term: string,\r\n UnitOfMeasure: string,\r\n UnitPrice: real\r\n)\r\n\r\n// AmortizedCosts_raw ingestion mapping\r\n.create-or-alter table AmortizedCosts_raw ingestion parquet mapping \"AmortizedCosts_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\r\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\r\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\r\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\r\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\r\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\r\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\r\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\r\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\r\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\r\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\r\n]\r\n```\r\n\r\n// AmortizedCosts_raw retention policy (clear historical data)\r\n.alter-merge table AmortizedCosts_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// AmortizedCosts_raw retention policy (set the user-defined retention period)\r\n.alter-merge table AmortizedCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable AmortizedCosts_raw streaming ingestion (required for Fabric)\r\n.alter table AmortizedCosts_raw policy streamingingestion disable\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_raw table -- Create the table if it doesn't exist\r\n.create-merge table CommitmentDiscountUsage_raw ( ignore: string )\r\n\r\n// CommitmentDiscountUsage_raw table -- Remove all columns to allow changing column types\r\n.alter table CommitmentDiscountUsage_raw ( ignore: string )\r\n\r\n// CommitmentDiscountUsage_raw table -- Redefine all columns\r\n.alter table CommitmentDiscountUsage_raw (\r\n InstanceFlexibilityGroup: string,\r\n InstanceFlexibilityRatio: real,\r\n InstanceId: string,\r\n Kind: string,\r\n ReservationId: string,\r\n ReservationOrderId: string,\r\n ReservedHours: real,\r\n SkuName: string,\r\n TotalReservedQuantity: real,\r\n UsageDate: datetime,\r\n UsedHours: real,\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// CommitmentDiscountUsage_raw ingestion mapping\r\n.create-or-alter table CommitmentDiscountUsage_raw ingestion parquet mapping \"CommitmentDiscountUsage_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\r\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\r\n { \"Column\": \"InstanceId\", \"Properties\": { \"Field\": \"InstanceId\" } },\r\n { \"Column\": \"Kind\", \"Properties\": { \"Field\": \"Kind\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\r\n { \"Column\": \"ReservedHours\", \"Properties\": { \"Field\": \"ReservedHours\" } },\r\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\r\n { \"Column\": \"TotalReservedQuantity\", \"Properties\": { \"Field\": \"TotalReservedQuantity\" } },\r\n { \"Column\": \"UsageDate\", \"Properties\": { \"Field\": \"UsageDate\" } },\r\n { \"Column\": \"UsedHours\", \"Properties\": { \"Field\": \"UsedHours\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// CommitmentDiscountUsage_raw retention policy (clear historical data)\r\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// CommitmentDiscountUsage_raw retention policy (set the user-defined retention period)\r\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable CommitmentDiscountUsage_raw streaming ingestion (required for Fabric)\r\n.alter table CommitmentDiscountUsage_raw policy streamingingestion disable\r\n\r\n\r\n//===| Costs |==========================================================================================================\r\n// Supported versions:\r\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n// - Tencent: 1.0 -- See https://www.tencentcloud.com/document/product/555/67495 / https://www.tencentcloud.com/document/product/555/67496\r\n// - Alibaba: 1.0 -- See https://www.alibabacloud.com/help/en/user-center/user-guide/export-alibaba-cloud-standard-billing-focus\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_raw table -- Create the table if it doesn't exist\r\n.create-merge table Costs_raw ( ignore: string )\r\n\r\n// Costs_raw table -- Remove all columns to allow changing column types\r\n.alter table Costs_raw ( ignore: string )\r\n\r\n// Costs_raw table -- Redefine all columns\r\n.alter table Costs_raw (\r\n AvailabilityZone: string, // FOCUS 0.5+\r\n BilledCost: real, // FOCUS 0.5+\r\n BillingAccountId: string, // FOCUS 0.5+\r\n BillingAccountName: string, // FOCUS 0.5+\r\n BillingAccountType: string, // Azure 1.0-preview(v1)+\r\n BillingCurrency: string, // FOCUS 0.5+\r\n BillingPeriodEnd: datetime, // FOCUS 0.5+\r\n BillingPeriodStart: datetime, // FOCUS 0.5+\r\n CapacityReservationId: string, // FOCUS 1.1+\r\n CapacityReservationStatus: string, // FOCUS 1.1+\r\n ChargeCategory: string, // FOCUS 1.0-preview+\r\n ChargeClass: string, // FOCUS 1.0+\r\n ChargeDescription: string, // FOCUS 1.0+\r\n ChargeFrequency: string, // FOCUS 1.0+\r\n ChargePeriodEnd: datetime, // FOCUS 0.5+\r\n ChargePeriodStart: datetime, // FOCUS 0.5+\r\n ChargeSubcategory: string, // FOCUS 1.0-preview only\r\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountId: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountName: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountQuantity: real, // FOCUS 1.1+\r\n CommitmentDiscountStatus: string, // FOCUS 1.0+\r\n CommitmentDiscountType: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountUnit: string, // FOCUS 1.1+\r\n ConsumedQuantity: real, // FOCUS 1.0+\r\n ConsumedUnit: string, // FOCUS 1.0+\r\n ContractedCost: real, // FOCUS 1.0+\r\n ContractedUnitPrice: real, // FOCUS 1.0+\r\n EffectiveCost: real, // FOCUS 1.0-preview+\r\n InvoiceId: string, // FOCUS 1.2+\r\n InvoiceIssuerName: string, // FOCUS 0.5+\r\n ListCost: real, // FOCUS 1.0-preview+\r\n ListUnitPrice: real, // FOCUS 1.0-preview+\r\n PricingCategory: string, // FOCUS 1.0-preview+\r\n PricingCurrency: string, // FOCUS 1.2+\r\n PricingQuantity: real, // FOCUS 1.0-preview+\r\n PricingUnit: string, // FOCUS 1.0-preview+\r\n ProviderName: string, // FOCUS 0.5+\r\n PublisherName: string, // FOCUS 0.5+\r\n Region: string, // FOCUS 0.5-1.0-preview (deprecated)\r\n RegionId: string, // FOCUS 1.0+\r\n RegionName: string, // FOCUS 1.0+\r\n ResourceId: string, // FOCUS 0.5+\r\n ResourceName: string, // FOCUS 0.5+\r\n ResourceType: string, // FOCUS 1.0-preview+\r\n ServiceCategory: string, // FOCUS 0.5+\r\n ServiceName: string, // FOCUS 0.5+\r\n ServiceSubcategory: string, // FOCUS 1.1+\r\n SkuId: string, // FOCUS 1.0-preview+\r\n SkuMeter: string, // FOCUS 1.1+\r\n SkuPriceDetails: string, // FOCUS 1.1+\r\n SkuPriceId: string, // FOCUS 1.0-preview+\r\n SubAccountId: string, // FOCUS 0.5+\r\n SubAccountName: string, // FOCUS 0.5+\r\n SubAccountType: string, // Azure 1.0-preview(v1)+\r\n Tags: string, // FOCUS 1.0-preview+\r\n UsageAmount: real, // GCP Jan 2024 -- Removed Mar 2024 (UsageQuantity)\r\n UsageQuantity: real, // FOCUS 1.0-preview only\r\n UsageUnit: string, // FOCUS 1.0-preview only\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_AmortizationClass: string, // Azure 1.2-preview+\r\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingItemCode: string, // Alibaba 1.0+\r\n x_BillingItemName: string, // Alibaba 1.0+\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_CommodityCode: string, // Alibaba 1.0+\r\n x_CommodityName: string, // Alibaba 1.0+\r\n x_ComponentName: string, // Tencent 1.0+\r\n x_ComponentType: string, // Tencent 1.0+\r\n x_ContractedCostInUsd: real, // Azure 1.0+\r\n x_Cost: real, // GCP Jan 2024 -- Removed Jun 2024 (ContractedCost)\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: string, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_CostType: string, // GCP Jan 2024\r\n x_Credits: string, // GCP Jan 2024\r\n x_CurrencyConversionRate: real, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: string, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0+\r\n x_InstanceID: string, // Alibaba 1.0+\r\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_OnDemandCost: real, // Azure 1.0-preview(v1) only\r\n x_OnDemandCostInUsd: real, // Azure 1.0-preview(v1) only\r\n x_OnDemandUnitPrice: real, // Azure 1.0-preview(v1) only\r\n x_Operation: string, // AWS 1.0\r\n x_OwnerAccountID: string, // Tencent 1.0+\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency: string, // Azure 1.0-preview(v1)-1.0r2\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServiceModel: string, // Azure 1.2-preview+\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: string, // Azure 1.0-preview(v1)-1.2-preview\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName: string, // Azure 1.0-preview(v1)-1.0r2\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuPlanName: string, // Azure 1.2-preview+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string, // Hubs v1_0+\r\n x_SubproductName: string, // Tencent 1.0+ // cSpell:ignore Subproduct\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Costs_raw ingestion mapping\r\n.create-or-alter table Costs_raw ingestion parquet mapping \"Costs_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BilledCost\", \"Properties\": { \"Field\": \"BilledCost\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingAccountType\", \"Properties\": { \"Field\": \"BillingAccountType\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEnd\", \"Properties\": { \"Field\": \"BillingPeriodEnd\" } },\r\n { \"Column\": \"BillingPeriodStart\", \"Properties\": { \"Field\": \"BillingPeriodStart\" } },\r\n { \"Column\": \"CapacityReservationId\", \"Properties\": { \"Field\": \"CapacityReservationId\" } },\r\n { \"Column\": \"CapacityReservationStatus\", \"Properties\": { \"Field\": \"CapacityReservationStatus\" } },\r\n { \"Column\": \"ChargeCategory\", \"Properties\": { \"Field\": \"ChargeCategory\" } },\r\n { \"Column\": \"ChargeClass\", \"Properties\": { \"Field\": \"ChargeClass\" } },\r\n { \"Column\": \"ChargeDescription\", \"Properties\": { \"Field\": \"ChargeDescription\" } },\r\n { \"Column\": \"ChargeFrequency\", \"Properties\": { \"Field\": \"ChargeFrequency\" } },\r\n { \"Column\": \"ChargePeriodEnd\", \"Properties\": { \"Field\": \"ChargePeriodEnd\" } },\r\n { \"Column\": \"ChargePeriodStart\", \"Properties\": { \"Field\": \"ChargePeriodStart\" } },\r\n { \"Column\": \"ChargeSubcategory\", \"Properties\": { \"Field\": \"ChargeSubcategory\" } },\r\n { \"Column\": \"CommitmentDiscountCategory\", \"Properties\": { \"Field\": \"CommitmentDiscountCategory\" } },\r\n { \"Column\": \"CommitmentDiscountId\", \"Properties\": { \"Field\": \"CommitmentDiscountId\" } },\r\n { \"Column\": \"CommitmentDiscountName\", \"Properties\": { \"Field\": \"CommitmentDiscountName\" } },\r\n { \"Column\": \"CommitmentDiscountQuantity\", \"Properties\": { \"Field\": \"CommitmentDiscountQuantity\" } },\r\n { \"Column\": \"CommitmentDiscountStatus\", \"Properties\": { \"Field\": \"CommitmentDiscountStatus\" } },\r\n { \"Column\": \"CommitmentDiscountType\", \"Properties\": { \"Field\": \"CommitmentDiscountType\" } },\r\n { \"Column\": \"CommitmentDiscountUnit\", \"Properties\": { \"Field\": \"CommitmentDiscountUnit\" } },\r\n { \"Column\": \"ConsumedQuantity\", \"Properties\": { \"Field\": \"ConsumedQuantity\" } },\r\n { \"Column\": \"ConsumedUnit\", \"Properties\": { \"Field\": \"ConsumedUnit\" } },\r\n { \"Column\": \"ContractedCost\", \"Properties\": { \"Field\": \"ContractedCost\" } },\r\n { \"Column\": \"ContractedUnitPrice\", \"Properties\": { \"Field\": \"ContractedUnitPrice\" } },\r\n { \"Column\": \"EffectiveCost\", \"Properties\": { \"Field\": \"EffectiveCost\" } },\r\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\r\n { \"Column\": \"InvoiceIssuerName\", \"Properties\": { \"Field\": \"InvoiceIssuerName\" } },\r\n { \"Column\": \"ListCost\", \"Properties\": { \"Field\": \"ListCost\" } },\r\n { \"Column\": \"ListUnitPrice\", \"Properties\": { \"Field\": \"ListUnitPrice\" } },\r\n { \"Column\": \"PricingCategory\", \"Properties\": { \"Field\": \"PricingCategory\" } },\r\n { \"Column\": \"PricingCurrency\", \"Properties\": { \"Field\": \"PricingCurrency\" } },\r\n { \"Column\": \"PricingQuantity\", \"Properties\": { \"Field\": \"PricingQuantity\" } },\r\n { \"Column\": \"PricingUnit\", \"Properties\": { \"Field\": \"PricingUnit\" } },\r\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\r\n { \"Column\": \"RegionId\", \"Properties\": { \"Field\": \"RegionId\" } },\r\n { \"Column\": \"RegionName\", \"Properties\": { \"Field\": \"RegionName\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\r\n { \"Column\": \"ServiceCategory\", \"Properties\": { \"Field\": \"ServiceCategory\" } },\r\n { \"Column\": \"ServiceName\", \"Properties\": { \"Field\": \"ServiceName\" } },\r\n { \"Column\": \"ServiceSubcategory\", \"Properties\": { \"Field\": \"ServiceSubcategory\" } },\r\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\r\n { \"Column\": \"SkuMeter\", \"Properties\": { \"Field\": \"SkuMeter\" } },\r\n { \"Column\": \"SkuPriceDetails\", \"Properties\": { \"Field\": \"SkuPriceDetails\" } },\r\n { \"Column\": \"SkuPriceId\", \"Properties\": { \"Field\": \"SkuPriceId\" } },\r\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\r\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\r\n { \"Column\": \"SubAccountType\", \"Properties\": { \"Field\": \"SubAccountType\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"UsageAmount\", \"Properties\": { \"Field\": \"UsageAmount\" } },\r\n { \"Column\": \"UsageQuantity\", \"Properties\": { \"Field\": \"UsageQuantity\" } },\r\n { \"Column\": \"UsageUnit\", \"Properties\": { \"Field\": \"UsageUnit\" } },\r\n { \"Column\": \"x_AccountId\", \"Properties\": { \"Field\": \"x_AccountId\" } },\r\n { \"Column\": \"x_AccountName\", \"Properties\": { \"Field\": \"x_AccountName\" } },\r\n { \"Column\": \"x_AccountOwnerId\", \"Properties\": { \"Field\": \"x_AccountOwnerId\" } },\r\n { \"Column\": \"x_AmortizationClass\", \"Properties\": { \"Field\": \"x_AmortizationClass\" } },\r\n { \"Column\": \"x_BilledCostInUsd\", \"Properties\": { \"Field\": \"x_BilledCostInUsd\" } },\r\n { \"Column\": \"x_BilledUnitPrice\", \"Properties\": { \"Field\": \"x_BilledUnitPrice\" } },\r\n { \"Column\": \"x_BillingAccountId\", \"Properties\": { \"Field\": \"x_BillingAccountId\" } },\r\n { \"Column\": \"x_BillingAccountName\", \"Properties\": { \"Field\": \"x_BillingAccountName\" } },\r\n { \"Column\": \"x_BillingExchangeRate\", \"Properties\": { \"Field\": \"x_BillingExchangeRate\" } },\r\n { \"Column\": \"x_BillingExchangeRateDate\", \"Properties\": { \"Field\": \"x_BillingExchangeRateDate\" } },\r\n { \"Column\": \"x_BillingItemCode\", \"Properties\": { \"Field\": \"x_BillingItemCode\" } },\r\n { \"Column\": \"x_BillingItemName\", \"Properties\": { \"Field\": \"x_BillingItemName\" } },\r\n { \"Column\": \"x_BillingProfileId\", \"Properties\": { \"Field\": \"x_BillingProfileId\" } },\r\n { \"Column\": \"x_BillingProfileName\", \"Properties\": { \"Field\": \"x_BillingProfileName\" } },\r\n { \"Column\": \"x_ChargeId\", \"Properties\": { \"Field\": \"x_ChargeId\" } },\r\n { \"Column\": \"x_ContractedCostInUsd\", \"Properties\": { \"Field\": \"x_ContractedCostInUsd\" } },\r\n { \"Column\": \"x_CommodityCode\", \"Properties\": { \"Field\": \"x_CommodityCode\" } },\r\n { \"Column\": \"x_CommodityName\", \"Properties\": { \"Field\": \"x_CommodityName\" } },\r\n { \"Column\": \"x_ComponentName\", \"Properties\": { \"Field\": \"x_ComponentName\" } },\r\n { \"Column\": \"x_ComponentType\", \"Properties\": { \"Field\": \"x_ComponentType\" } },\r\n { \"Column\": \"x_Cost\", \"Properties\": { \"Field\": \"x_Cost\" } },\r\n { \"Column\": \"x_CostAllocationRuleName\", \"Properties\": { \"Field\": \"x_CostAllocationRuleName\" } },\r\n { \"Column\": \"x_CostCategories\", \"Properties\": { \"Field\": \"x_CostCategories\" } },\r\n { \"Column\": \"x_CostCenter\", \"Properties\": { \"Field\": \"x_CostCenter\" } },\r\n { \"Column\": \"x_Credits\", \"Properties\": { \"Field\": \"x_Credits\" } },\r\n { \"Column\": \"x_CostType\", \"Properties\": { \"Field\": \"x_CostType\" } },\r\n { \"Column\": \"x_CurrencyConversionRate\", \"Properties\": { \"Field\": \"x_CurrencyConversionRate\" } },\r\n { \"Column\": \"x_CustomerId\", \"Properties\": { \"Field\": \"x_CustomerId\" } },\r\n { \"Column\": \"x_CustomerName\", \"Properties\": { \"Field\": \"x_CustomerName\" } },\r\n { \"Column\": \"x_Discount\", \"Properties\": { \"Field\": \"x_Discount\" } },\r\n { \"Column\": \"x_EffectiveCostInUsd\", \"Properties\": { \"Field\": \"x_EffectiveCostInUsd\" } },\r\n { \"Column\": \"x_EffectiveUnitPrice\", \"Properties\": { \"Field\": \"x_EffectiveUnitPrice\" } },\r\n { \"Column\": \"x_ExportTime\", \"Properties\": { \"Field\": \"x_ExportTime\" } },\r\n { \"Column\": \"x_InstanceID\", \"Properties\": { \"Field\": \"x_InstanceID\" } },\r\n { \"Column\": \"x_InvoiceId\", \"Properties\": { \"Field\": \"x_InvoiceId\" } },\r\n { \"Column\": \"x_InvoiceIssuerId\", \"Properties\": { \"Field\": \"x_InvoiceIssuerId\" } },\r\n { \"Column\": \"x_InvoiceSectionId\", \"Properties\": { \"Field\": \"x_InvoiceSectionId\" } },\r\n { \"Column\": \"x_InvoiceSectionName\", \"Properties\": { \"Field\": \"x_InvoiceSectionName\" } },\r\n { \"Column\": \"x_ListCostInUsd\", \"Properties\": { \"Field\": \"x_ListCostInUsd\" } },\r\n { \"Column\": \"x_Location\", \"Properties\": { \"Field\": \"x_Location\" } },\r\n { \"Column\": \"x_OnDemandCost\", \"Properties\": { \"Field\": \"x_OnDemandCost\" } },\r\n { \"Column\": \"x_OnDemandCostInUsd\", \"Properties\": { \"Field\": \"x_OnDemandCostInUsd\" } },\r\n { \"Column\": \"x_OnDemandUnitPrice\", \"Properties\": { \"Field\": \"x_OnDemandUnitPrice\" } },\r\n { \"Column\": \"x_Operation\", \"Properties\": { \"Field\": \"x_Operation\" } },\r\n { \"Column\": \"x_OwnerAccountID\", \"Properties\": { \"Field\": \"x_OwnerAccountID\" } },\r\n { \"Column\": \"x_PartnerCreditApplied\", \"Properties\": { \"Field\": \"x_PartnerCreditApplied\" } },\r\n { \"Column\": \"x_PartnerCreditRate\", \"Properties\": { \"Field\": \"x_PartnerCreditRate\" } },\r\n { \"Column\": \"x_PricingBlockSize\", \"Properties\": { \"Field\": \"x_PricingBlockSize\" } },\r\n { \"Column\": \"x_PricingCurrency\", \"Properties\": { \"Field\": \"x_PricingCurrency\" } },\r\n { \"Column\": \"x_PricingSubcategory\", \"Properties\": { \"Field\": \"x_PricingSubcategory\" } },\r\n { \"Column\": \"x_PricingUnitDescription\", \"Properties\": { \"Field\": \"x_PricingUnitDescription\" } },\r\n { \"Column\": \"x_Project\", \"Properties\": { \"Field\": \"x_Project\" } },\r\n { \"Column\": \"x_PublisherCategory\", \"Properties\": { \"Field\": \"x_PublisherCategory\" } },\r\n { \"Column\": \"x_PublisherId\", \"Properties\": { \"Field\": \"x_PublisherId\" } },\r\n { \"Column\": \"x_ResellerId\", \"Properties\": { \"Field\": \"x_ResellerId\" } },\r\n { \"Column\": \"x_ResellerName\", \"Properties\": { \"Field\": \"x_ResellerName\" } },\r\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\r\n { \"Column\": \"x_ResourceType\", \"Properties\": { \"Field\": \"x_ResourceType\" } },\r\n { \"Column\": \"x_ServiceCode\", \"Properties\": { \"Field\": \"x_ServiceCode\" } },\r\n { \"Column\": \"x_ServiceId\", \"Properties\": { \"Field\": \"x_ServiceId\" } },\r\n { \"Column\": \"x_ServiceModel\", \"Properties\": { \"Field\": \"x_ServiceModel\" } },\r\n { \"Column\": \"x_ServicePeriodEnd\", \"Properties\": { \"Field\": \"x_ServicePeriodEnd\" } },\r\n { \"Column\": \"x_ServicePeriodStart\", \"Properties\": { \"Field\": \"x_ServicePeriodStart\" } },\r\n { \"Column\": \"x_SkuDescription\", \"Properties\": { \"Field\": \"x_SkuDescription\" } },\r\n { \"Column\": \"x_SkuDetails\", \"Properties\": { \"Field\": \"x_SkuDetails\" } },\r\n { \"Column\": \"x_SkuIsCreditEligible\", \"Properties\": { \"Field\": \"x_SkuIsCreditEligible\" } },\r\n { \"Column\": \"x_SkuMeterCategory\", \"Properties\": { \"Field\": \"x_SkuMeterCategory\" } },\r\n { \"Column\": \"x_SkuMeterId\", \"Properties\": { \"Field\": \"x_SkuMeterId\" } },\r\n { \"Column\": \"x_SkuMeterName\", \"Properties\": { \"Field\": \"x_SkuMeterName\" } },\r\n { \"Column\": \"x_SkuMeterSubcategory\", \"Properties\": { \"Field\": \"x_SkuMeterSubcategory\" } },\r\n { \"Column\": \"x_SkuOfferId\", \"Properties\": { \"Field\": \"x_SkuOfferId\" } },\r\n { \"Column\": \"x_SkuOrderId\", \"Properties\": { \"Field\": \"x_SkuOrderId\" } },\r\n { \"Column\": \"x_SkuOrderName\", \"Properties\": { \"Field\": \"x_SkuOrderName\" } },\r\n { \"Column\": \"x_SkuPartNumber\", \"Properties\": { \"Field\": \"x_SkuPartNumber\" } },\r\n { \"Column\": \"x_SkuPlanName\", \"Properties\": { \"Field\": \"x_SkuPlanName\" } },\r\n { \"Column\": \"x_SkuRegion\", \"Properties\": { \"Field\": \"x_SkuRegion\" } },\r\n { \"Column\": \"x_SkuServiceFamily\", \"Properties\": { \"Field\": \"x_SkuServiceFamily\" } },\r\n { \"Column\": \"x_SkuTerm\", \"Properties\": { \"Field\": \"x_SkuTerm\" } },\r\n { \"Column\": \"x_SkuTier\", \"Properties\": { \"Field\": \"x_SkuTier\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } },\r\n { \"Column\": \"x_SubproductName\", \"Properties\": { \"Field\": \"x_SubproductName\" } },\r\n { \"Column\": \"x_UsageType\", \"Properties\": { \"Field\": \"x_UsageType\" } }\r\n]\r\n```\r\n\r\n// Costs_raw retention policy (clear historical data)\r\n.alter-merge table Costs_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Costs_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Costs_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Costs_raw streaming ingestion (required for Fabric)\r\n.alter table Costs_raw policy streamingingestion disable\r\n\r\n\r\n//===| Prices |=========================================================================================================\r\n// NOTE: Must be before cost details.\r\n//\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_raw table -- Create the table if it doesn't exist\r\n.create-merge table Prices_raw ( ignore: string )\r\n\r\n// Prices_raw table -- Remove all columns to allow changing column types\r\n.alter table Prices_raw ( ignore: string )\r\n\r\n// Prices_raw table -- Redefine all columns\r\n.alter table Prices_raw (\r\n BasePrice: real, // Azure EA + MCA\r\n BillingAccountId: string, // Azure MCA\r\n BillingAccountName: string, // Azure MCA\r\n BillingCurrency: string, // Azure MCA\r\n BillingProfileId: string, // Azure MCA\r\n BillingProfileName: string, // Azure MCA\r\n Currency: string, // Azure MCA\r\n CurrencyCode: string, // Azure EA\r\n EffectiveEndDate: datetime, // Azure MCA\r\n EffectiveStartDate: datetime, // Azure EA + MCA\r\n EnrollmentNumber: string, // Azure EA\r\n IncludedQuantity: real, // Azure EA\r\n MarketPrice: real, // Azure EA + MCA\r\n MeterCategory: string, // Azure EA + MCA\r\n MeterId: string, // Azure MCA\r\n MeterID: string, // Azure EA\r\n MeterName: string, // Azure EA + MCA\r\n MeterRegion: string, // Azure EA + MCA\r\n MeterSubCategory: string, // Azure EA + MCA\r\n MeterType: string, // Azure EA + MCA\r\n OfferID: string, // Azure EA\r\n PartNumber: string, // Azure EA\r\n PriceType: string, // Azure EA + MCA\r\n Product: string, // Azure EA + MCA\r\n ProductId: string, // Azure MCA\r\n ProductID: string, // Azure EA\r\n ServiceFamily: string, // Azure EA + MCA\r\n SkuId: string, // Azure MCA\r\n SkuID: string, // Azure EA\r\n Term: string, // Azure EA + MCA\r\n TierMinimumUnits: real, // Azure MCA\r\n UnitOfMeasure: string, // Azure EA + MCA\r\n UnitPrice: real, // Azure EA + MCA\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Prices_raw ingestion mapping\r\n.create-or-alter table Prices_raw ingestion parquet mapping \"Prices_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"BasePrice\", \"Properties\": { \"Field\": \"BasePrice\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\r\n { \"Column\": \"CurrencyCode\", \"Properties\": { \"Field\": \"CurrencyCode\" } },\r\n { \"Column\": \"EffectiveEndDate\", \"Properties\": { \"Field\": \"EffectiveEndDate\" } },\r\n { \"Column\": \"EffectiveStartDate\", \"Properties\": { \"Field\": \"EffectiveStartDate\" } },\r\n { \"Column\": \"EnrollmentNumber\", \"Properties\": { \"Field\": \"EnrollmentNumber\" } },\r\n { \"Column\": \"IncludedQuantity\", \"Properties\": { \"Field\": \"IncludedQuantity\" } },\r\n { \"Column\": \"MarketPrice\", \"Properties\": { \"Field\": \"MarketPrice\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterID\", \"Properties\": { \"Field\": \"MeterID\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"MeterType\", \"Properties\": { \"Field\": \"MeterType\" } },\r\n { \"Column\": \"OfferID\", \"Properties\": { \"Field\": \"OfferID\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PriceType\", \"Properties\": { \"Field\": \"PriceType\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductId\", \"Properties\": { \"Field\": \"ProductId\" } },\r\n { \"Column\": \"ProductID\", \"Properties\": { \"Field\": \"ProductID\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\r\n { \"Column\": \"SkuID\", \"Properties\": { \"Field\": \"SkuID\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"TierMinimumUnits\", \"Properties\": { \"Field\": \"TierMinimumUnits\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Prices_raw retention policy (clear historical data)\r\n.alter-merge table Prices_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Prices_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Prices_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Prices_raw streaming ingestion (required for Fabric)\r\n.alter table Prices_raw policy streamingingestion disable\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_raw table -- Create the table if it doesn't exist\r\n.create-merge table Recommendations_raw ( ignore: string )\r\n\r\n// Recommendations_raw table -- Remove all columns to allow changing column types\r\n.alter table Recommendations_raw ( ignore: string )\r\n\r\n// Recommendations_raw table -- Redefine all columns\r\n.alter table Recommendations_raw (\r\n CostWithNoReservedInstances: real, // MS CM EA resv reco 2024-05-01\r\n CostWithNoReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n FirstUsageDate: datetime, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n InstanceFlexibilityGroup: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n InstanceFlexibilityRatio: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n Location: string, // MS CM EA+MCA resv reco 2024-05-01\r\n LookBackPeriod: string, // MS CM EA+MCA resv reco 2024-05-01\r\n MeterId: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n NetSavings: real, // MS CM EA resv reco 2024-05-01\r\n NetSavingsJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n NormalizedSize: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n ProviderName: string, // Hubs v1_2\r\n RecommendedQuantity: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n RecommendedQuantityNormalized: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n ResourceId: string, // Hubs v1_2\r\n ResourceName: string, // Hubs v1_2\r\n ResourceType: string, // Hubs v1_2, MS CM EA+MCA resv reco 2024-05-01\r\n Scope: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n SKU: string, // MS CM EA resv reco 2024-05-01\r\n SkuName: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces\r\n SkuProperties: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n SubAccountId: string, // Hubs v1_2\r\n SubAccountName: string, // Hubs v1_2\r\n SubscriptionId: string, // MS CM EA+MCA resv reco 2024-05-01\r\n Term: string, // MS CM EA+MCA resv reco 2024-05-01\r\n TotalCostWithReservedInstances: real, // MS CM EA resv reco 2024-05-01\r\n TotalCostWithReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n x_EffectiveCostAfter: real, // Hubs v1_2\r\n x_EffectiveCostBefore: real, // Hubs v1_2\r\n x_EffectiveCostSavings: real, // Hubs v1_2\r\n x_RecommendationCategory: string, // Hubs v1_2\r\n x_RecommendationDate: datetime, // Hubs v1_2\r\n x_RecommendationDescription: string, // Hubs v1_2\r\n x_RecommendationDetails: dynamic, // Hubs v1_2\r\n x_RecommendationId: string, // Hubs v1_2\r\n x_ResourceGroupName: string, // Hubs v1_2\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Recommendations_raw ingestion mapping\r\n.create-or-alter table Recommendations_raw ingestion parquet mapping \"Recommendations_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"CostWithNoReservedInstances\", \"Properties\": { \"Field\": \"CostWithNoReservedInstances\" } },\r\n { \"Column\": \"CostWithNoReservedInstancesJson\", \"Properties\": { \"Field\": \"CostWithNoReservedInstancesJson\" } },\r\n { \"Column\": \"FirstUsageDate\", \"Properties\": { \"Field\": \"FirstUsageDate\" } },\r\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\r\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\r\n { \"Column\": \"Location\", \"Properties\": { \"Field\": \"Location\" } },\r\n { \"Column\": \"LookBackPeriod\", \"Properties\": { \"Field\": \"LookBackPeriod\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"NetSavings\", \"Properties\": { \"Field\": \"NetSavings\" } },\r\n { \"Column\": \"NetSavingsJson\", \"Properties\": { \"Field\": \"NetSavingsJson\" } },\r\n { \"Column\": \"NormalizedSize\", \"Properties\": { \"Field\": \"NormalizedSize\" } },\r\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\r\n { \"Column\": \"RecommendedQuantity\", \"Properties\": { \"Field\": \"RecommendedQuantity\" } },\r\n { \"Column\": \"RecommendedQuantityNormalized\", \"Properties\": { \"Field\": \"RecommendedQuantityNormalized\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\r\n { \"Column\": \"Scope\", \"Properties\": { \"Field\": \"Scope\" } },\r\n { \"Column\": \"SKU\", \"Properties\": { \"Field\": \"SKU\" } },\r\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\r\n { \"Column\": \"SkuProperties\", \"Properties\": { \"Field\": \"SkuProperties\" } },\r\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\r\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"TotalCostWithReservedInstances\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstances\" } },\r\n { \"Column\": \"TotalCostWithReservedInstancesJson\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstancesJson\" } },\r\n { \"Column\": \"x_EffectiveCostAfter\", \"Properties\": { \"Field\": \"x_EffectiveCostAfter\" } },\r\n { \"Column\": \"x_EffectiveCostBefore\", \"Properties\": { \"Field\": \"x_EffectiveCostBefore\" } },\r\n { \"Column\": \"x_EffectiveCostSavings\", \"Properties\": { \"Field\": \"x_EffectiveCostSavings\" } },\r\n { \"Column\": \"x_RecommendationCategory\", \"Properties\": { \"Field\": \"x_RecommendationCategory\" } },\r\n { \"Column\": \"x_RecommendationDate\", \"Properties\": { \"Field\": \"x_RecommendationDate\" } },\r\n { \"Column\": \"x_RecommendationDescription\", \"Properties\": { \"Field\": \"x_RecommendationDescription\" } },\r\n { \"Column\": \"x_RecommendationDetails\", \"Properties\": { \"Field\": \"x_RecommendationDetails\" } },\r\n { \"Column\": \"x_RecommendationId\", \"Properties\": { \"Field\": \"x_RecommendationId\" } },\r\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Recommendations_raw retention policy (clear historical data)\r\n.alter-merge table Recommendations_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Recommendations_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Recommendations_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Recommendations_raw streaming ingestion (required for Fabric)\r\n.alter table Recommendations_raw policy streamingingestion disable\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_raw table -- Create the table if it doesn't exist\r\n.create-merge table Transactions_raw ( ignore: string )\r\n\r\n// Transactions_raw table -- Remove all columns to allow changing column types\r\n.alter table Transactions_raw ( ignore: string )\r\n\r\n// Transactions_raw table -- Redefine all columns\r\n.alter table Transactions_raw (\r\n AccountName: string, // MS CM EA resv trans 2023-05-01\r\n AccountOwnerEmail: string, // MS CM EA resv trans 2023-05-01\r\n Amount: real, // MS CM EA+MCA resv trans 2023-05-01\r\n ArmSkuName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n BillingFrequency: string, // MS CM EA+MCA resv trans 2023-05-01\r\n BillingMonth: string, // MS CM EA resv trans 2023-05-01\r\n BillingProfileId: string, // MS CM MCA resv trans 2023-05-01\r\n BillingProfileName: string, // MS CM MCA resv trans 2023-05-01\r\n CostCenter: string, // MS CM EA resv trans 2023-05-01\r\n Currency: string, // MS CM EA+MCA resv trans 2023-05-01\r\n CurrentEnrollmentId: string, // MS CM EA resv trans 2023-05-01\r\n DepartmentName: string, // MS CM EA resv trans 2023-05-01\r\n Description: string, // MS CM EA+MCA resv trans 2023-05-01\r\n EventDate: datetime, // MS CM EA+MCA resv trans 2023-05-01\r\n EventType: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Invoice: string, // MS CM EA+MCA resv trans 2023-05-01\r\n InvoiceId: string, // MS CM EA+MCA resv trans 2023-05-01\r\n InvoiceSectionId: string, // MS CM MCA resv trans 2023-05-01\r\n InvoiceSectionName: string, // MS CM MCA resv trans 2023-05-01\r\n MonetaryCommitment: real, // MS CM EA resv trans 2023-05-01\r\n Overage: real, // MS CM EA resv trans 2023-05-01\r\n PurchasingEnrollment: string, // MS CM EA resv trans 2023-05-01\r\n PurchasingSubscriptionGuid: string, // MS CM EA+MCA resv trans 2023-05-01\r\n PurchasingSubscriptionName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Quantity: real, // MS CM EA+MCA resv trans 2023-05-01\r\n Region: string, // MS CM EA+MCA resv trans 2023-05-01\r\n ReservationOrderId: string, // MS CM EA+MCA resv trans 2023-05-01\r\n ReservationOrderName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Term: string, // MS CM EA+MCA resv trans 2023-05-01\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Transactions_raw ingestion mapping\r\n.create-or-alter table Transactions_raw ingestion parquet mapping \"Transactions_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerEmail\", \"Properties\": { \"Field\": \"AccountOwnerEmail\" } },\r\n { \"Column\": \"Amount\", \"Properties\": { \"Field\": \"Amount\" } },\r\n { \"Column\": \"ArmSkuName\", \"Properties\": { \"Field\": \"ArmSkuName\" } },\r\n { \"Column\": \"BillingFrequency\", \"Properties\": { \"Field\": \"BillingFrequency\" } },\r\n { \"Column\": \"BillingMonth\", \"Properties\": { \"Field\": \"BillingMonth\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\r\n { \"Column\": \"CurrentEnrollmentId\", \"Properties\": { \"Field\": \"CurrentEnrollmentId\" } },\r\n { \"Column\": \"DepartmentName\", \"Properties\": { \"Field\": \"DepartmentName\" } },\r\n { \"Column\": \"Description\", \"Properties\": { \"Field\": \"Description\" } },\r\n { \"Column\": \"EventDate\", \"Properties\": { \"Field\": \"EventDate\" } },\r\n { \"Column\": \"EventType\", \"Properties\": { \"Field\": \"EventType\" } },\r\n { \"Column\": \"Invoice\", \"Properties\": { \"Field\": \"Invoice\" } },\r\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"InvoiceSectionName\", \"Properties\": { \"Field\": \"InvoiceSectionName\" } },\r\n { \"Column\": \"MonetaryCommitment\", \"Properties\": { \"Field\": \"MonetaryCommitment\" } },\r\n { \"Column\": \"Overage\", \"Properties\": { \"Field\": \"Overage\" } },\r\n { \"Column\": \"PurchasingEnrollment\", \"Properties\": { \"Field\": \"PurchasingEnrollment\" } },\r\n { \"Column\": \"PurchasingSubscriptionGuid\", \"Properties\": { \"Field\": \"PurchasingSubscriptionGuid\" } },\r\n { \"Column\": \"PurchasingSubscriptionName\", \"Properties\": { \"Field\": \"PurchasingSubscriptionName\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\r\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\r\n { \"Column\": \"ReservationOrderName\", \"Properties\": { \"Field\": \"ReservationOrderName\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Transactions_raw retention policy (clear historical data)\r\n.alter-merge table Transactions_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Transactions_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Transactions_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Transactions_raw streaming ingestion (required for Fabric)\r\n.alter table Transactions_raw policy streamingingestion disable\r\n\r\n", - "$fxv#9": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Prices |=========================================================================================================\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All prices transformed to FOCUS 1.0. Use Prices_transform_v1_2() instead.', folder='Prices')\r\nPrices_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n let prices = materialize(\r\n Prices_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n BasePrice = todecimal(BasePrice),\r\n IncludedQuantity = todecimal(IncludedQuantity),\r\n MarketPrice = todecimal(MarketPrice),\r\n TierMinimumUnits = todecimal(TierMinimumUnits),\r\n UnitPrice = todecimal(UnitPrice)\r\n //\r\n | extend x_SkuId = coalesce(SkuId, SkuID)\r\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\r\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\r\n | extend x_SkuTerm = isoMonths(Term)\r\n | project-rename\r\n x_BaseUnitPrice = BasePrice,\r\n x_EffectivePeriodEnd = EffectiveEndDate,\r\n x_EffectivePeriodStart = EffectiveStartDate,\r\n x_PricingUnitDescription = UnitOfMeasure,\r\n x_SkuIncludedQuantity = IncludedQuantity,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuMeterType = MeterType,\r\n x_SkuOfferId = OfferID,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPriceType = PriceType,\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTier = TierMinimumUnits\r\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, todecimal('')) // UnitPrice for savings plan is not the on-demand unit price\r\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, todecimal('')) // MarketPrice for savings plan is not the list price\r\n | extend ChargeCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Usage',\r\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\r\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\r\n ''\r\n )\r\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\r\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\r\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\r\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\r\n //\r\n // Get latest ingested row based on the unique ID\r\n | extend x_IngestionTime = ingestion_time()\r\n );\r\n //\r\n // Meters for reservations and savings plans to identify commitment eligibility\r\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\r\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\r\n //\r\n // Copy list/base/contracted prices from on-demand SKUs\r\n prices\r\n | where x_SkuPriceType == 'SavingsPlan'\r\n // If we use join, specify the shuffle key\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\r\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\r\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\r\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\r\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\r\n //\r\n // Calculate commitment discount elgibility\r\n // TODO: Would a join be faster?\r\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\r\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\r\n //\r\n // Add PricingUnit and x_PricingBlockSize\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\r\n | lookup kind=leftouter (PricingUnits | extend x_PricingBlockSize = todecimal(x_PricingBlockSize)) on x_PricingUnitDescription\r\n //\r\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, todecimal('')) // Savings plan prices are for the effective price, not the contracted price\r\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\r\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\r\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\r\n | project\r\n BillingAccountId = tolower(case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n BillingAccountId startswith '/', BillingAccountId,\r\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\r\n )),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\r\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\r\n ChargeCategory,\r\n CommitmentDiscountCategory = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Usage',\r\n x_SkuPriceType == 'SavingsPlan', 'Spend',\r\n ''\r\n ),\r\n CommitmentDiscountType = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\r\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\r\n ''\r\n ),\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed',\r\n ''\r\n ),\r\n PricingUnit,\r\n SkuId = coalesce(ProductId, ProductID),\r\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n strlen(x_BillingAccountId) > 32, 'MCA',\r\n strlen(x_BillingAccountId) < 32, 'EA',\r\n 'Unknown'\r\n ),\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\r\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingCurrency = coalesce(Currency, CurrencyCode), // CurrencyCode last as a fallback only\r\n x_PricingSubcategory = case(\r\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_SkuDescription = Product,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\r\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\r\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\r\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\r\n}\r\n\r\n// Prices_final_v1_0 table\r\n.create-merge table Prices_final_v1_0 (\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n ChargeCategory: string,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountType: string,\r\n ContractedUnitPrice: decimal,\r\n ListUnitPrice: decimal,\r\n PricingCategory: string,\r\n PricingUnit: string,\r\n SkuId: string,\r\n SkuPriceId: string,\r\n SkuPriceIdv2: string, // Hubs add-on\r\n x_BaseUnitPrice: decimal, // Azure\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure MCA\r\n x_BillingProfileId: string, // Azure MCA\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_ContractedUnitPriceDiscount: decimal, // Hubs add-on\r\n x_ContractedUnitPriceDiscountPercent: decimal, // Hubs add-on\r\n x_EffectivePeriodEnd: datetime, // Azure\r\n x_EffectivePeriodStart: datetime, // Azure\r\n x_EffectiveUnitPrice: decimal, // Azure\r\n x_EffectiveUnitPriceDiscount: decimal, // Hubs add-on\r\n x_EffectiveUnitPriceDiscountPercent: decimal, // Hubs add-on\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_PricingBlockSize: decimal, // Hubs add-on\r\n x_PricingCurrency: string, // Azure\r\n x_PricingSubcategory: string, // Hubs add-on\r\n x_PricingUnitDescription: string, // Azure\r\n x_SkuDescription: string, // Azure\r\n x_SkuId: string, // Azure\r\n x_SkuIncludedQuantity: decimal, // Azure EA\r\n x_SkuMeterCategory: string, // Azure\r\n x_SkuMeterId: string, // Azure\r\n x_SkuMeterName: string, // Azure\r\n x_SkuMeterSubcategory: string, // Azure\r\n x_SkuMeterType: string, // Azure\r\n x_SkuPriceType: string, // Azure\r\n x_SkuProductId: string, // Azure\r\n x_SkuRegion: string, // Azure\r\n x_SkuServiceFamily: string, // Azure\r\n x_SkuOfferId: string, // Azure EA\r\n x_SkuPartNumber: string, // Azure EA\r\n x_SkuTerm: int, // Azure\r\n x_SkuTier: decimal, // Azure MCA\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_TotalUnitPriceDiscount: decimal, // Hubs add-on\r\n x_TotalUnitPriceDiscountPercent: decimal // Hubs add-on\r\n)\r\n\r\n// Update policy for Prices_raw -> Prices_final_v1_0\r\n.alter table Prices_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Prices_raw\",\r\n \"Query\": \"Prices_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Cost and usage |=================================================================================================\r\n// Supported versions:\r\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All costs transformed to FOCUS 1.0. Use Costs_transform_v1_2() instead.', folder='Costs')\r\nCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n Costs_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n ContractedCost = todecimal(ContractedCost),\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n EffectiveCost = todecimal(EffectiveCost),\r\n ListCost = todecimal(ListCost),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n UsageAmount = todecimal(UsageAmount),\r\n UsageQuantity = todecimal(UsageQuantity),\r\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\r\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\r\n x_Cost = todecimal(x_Cost),\r\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\r\n x_OnDemandCost = todecimal(x_OnDemandCost),\r\n x_OnDemandCostInUsd = todecimal(x_OnDemandCostInUsd),\r\n x_OnDemandUnitPrice = todecimal(x_OnDemandUnitPrice),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\r\n //\r\n // Dedupe rows\r\n | extend x_IngestionTime = ingestion_time()\r\n | extend x_ChargeId = ''\r\n // TODO: Consider adding a unique charge ID per row\r\n // hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // // 1. Resource hierarchy (including resource name), highest to lowest\r\n // BillingAccountId,\r\n // x_InvoiceSectionId,\r\n // x_AccountOwnerId,\r\n // SubAccountId,\r\n // x_ResourceGroupName,\r\n // ResourceName,\r\n // // 2. Resource details\r\n // ResourceId,\r\n // RegionId,\r\n // Tags,\r\n // CommitmentDiscountId,\r\n // x_CostCenter,\r\n // // 4. Meter details\r\n // SkuPriceId,\r\n // x_SkuMeterId,\r\n // x_SkuPartNumber,\r\n // x_SkuOfferId,\r\n // x_SkuDetails,\r\n // // 5. Date\r\n // ChargePeriodStart\r\n // ))\r\n //\r\n // Identify data quality issues\r\n | extend x_SourceChanges = trim_end(',', strcat(\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\r\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\r\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\r\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\r\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\r\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\r\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\r\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\r\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\r\n 'XEffectiveUnitPriceRoundingError,', ''),\r\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\r\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\r\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\r\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\r\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\r\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\r\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\r\n ))\r\n //\r\n // Fix columns needed in other changes\r\n | extend ProviderName = case(\r\n isnotempty(ProviderName), ProviderName,\r\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\r\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\r\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\r\n ''\r\n )\r\n //\r\n // Identify source\r\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\r\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\r\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\r\n ''\r\n ))\r\n // Append version check error code\r\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\r\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\r\n )\r\n //\r\n // Fix quantities\r\n | extend PricingQuantity = case(\r\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\r\n PricingQuantity\r\n )\r\n | extend ConsumedQuantity = case(\r\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\r\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, decimal(1)),\r\n ConsumedQuantity\r\n )\r\n //\r\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\r\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\r\n and (ListUnitPrice == 0 or ContractedUnitPrice == 0)\r\n and x_EffectiveUnitPrice != 0\r\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\r\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\r\n | as allCosts\r\n | where tmp_MissingPrices\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | as costsWithMissingPrices\r\n | join kind=leftouter (\r\n Prices_final_v1_0\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\r\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\r\n ) on tmp_ReservationPriceLookupKey\r\n //\r\n // Select the best price to use for each row\r\n // TODO: Save values before changing -- | extend x_old_ContractedUnitPrice = ContractedUnitPrice, x_old_EffectiveUnitPrice = x_EffectiveUnitPrice, x_old_ListUnitPrice = ListUnitPrice, x_old_ListCost = ListCost, x_old_ContractedCost = ContractedCost\r\n | extend x_EffectiveUnitPrice = case(\r\n // If price is a rounding error away from the billed price, use the billed price\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\r\n // If price is a rounding error away from the contracted price, use the contracted price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ContractedUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\r\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ListUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // Otherwise, assume the contracted price is the same as list price to support aggregations\r\n ContractedUnitPrice\r\n )\r\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\r\n | extend ContractedCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\r\n // ContractedCost is 0 in all other scenarios...\r\n // If 0 and there's a billed cost and prices are the same, use BilledCost\r\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\r\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\r\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume EffectiveCost\r\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ContractedCost\r\n )\r\n | extend ListCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\r\n // ListCost is 0 in all other scenarios...\r\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\r\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume ContractedCost\r\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ListCost\r\n )\r\n // Merge the rest of the unmodified cost records and remove excess columns\r\n | union (allCosts | where not(tmp_MissingPrices))\r\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\r\n //\r\n // BUG: Fix ContractedCost that has bad values\r\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\r\n //\r\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\r\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), todecimal(''))\r\n | extend ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\r\n //\r\n // Convert IDs to lowercase for consistency\r\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\r\n //\r\n // BUG: Remove EffectiveCost for commitment discount purchases\r\n | extend EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), EffectiveCost)\r\n | extend x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), x_EffectiveCostInUsd)\r\n //\r\n // Clean up resource columns\r\n | extend ResourceId = case(\r\n isnotempty(ResourceId), ResourceId,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\r\n ResourceId)\r\n | extend ResourceName = tolower(case(\r\n isnotempty(ResourceName), ResourceName,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\r\n ResourceName))\r\n | extend x_ResourceType = case(\r\n isnotempty(x_ResourceType), x_ResourceType,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\r\n x_ResourceType)\r\n | extend ResourceType = case(\r\n // Use existing resource type display name unless it's an internal resource type ID\r\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\r\n // Use CommitmentDiscountType for commitment discount purchases\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\r\n // Look up display name from internal type\r\n isnotempty(x_ResourceType), coalesce(resource_type(x_ResourceType).SingularDisplayName, ResourceType, x_ResourceType),\r\n ResourceType)\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId = tolower(BillingAccountId),\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEnd),\r\n BillingPeriodStart = startofmonth(BillingPeriodStart),\r\n ChargeCategory = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Credit', 'Credit',\r\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\r\n ChargeCategory\r\n ),\r\n ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass),\r\n ChargeDescription,\r\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\r\n ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency),\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = tolower(CommitmentDiscountId),\r\n CommitmentDiscountName,\r\n CommitmentDiscountStatus = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Used Commitment', 'Used',\r\n ChargeSubcategory == 'Unused Commitment', 'Unused',\r\n CommitmentDiscountStatus\r\n ),\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\r\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\r\n EffectiveCost,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n // Handle FOCUS 1.0-preview PricingCategory values\r\n PricingCategory == 'On-Demand', 'Standard',\r\n PricingCategory == 'Commitment-Based', 'Committed',\r\n PricingCategory\r\n ),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n // Handle missing PublisherName values\r\n PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, ''),\r\n // Handle FOCUS 1.0-preview Region column\r\n RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region)),\r\n RegionName = coalesce(RegionName, Region),\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SkuId,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType, // Azure 1.0-preview(v1)+\r\n Tags = parse_json(Tags),\r\n x_AccountId, // Azure 1.0-preview(v1)+\r\n x_AccountName, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId, // Azure 1.0-preview(v1)+\r\n x_BilledCostInUsd, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement = case(\r\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\r\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\r\n ProviderName\r\n ), // Hubs add-on\r\n x_BillingAccountId, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate, // Azure 1.0-preview(v1)+\r\n x_BillingProfileId, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName, // Azure 1.0-preview(v1)+\r\n x_ChargeId, // Azure 1.0-preview(v1) only\r\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd), // Azure 1.0+\r\n x_CostAllocationRuleName, // Azure 1.0-preview(v1)+\r\n x_CostCategories = parse_json(x_CostCategories), // AWS 1.0 (JSON)\r\n x_CostCenter, // Azure 1.0-preview(v1)+\r\n x_Credits = parse_json(x_Credits), // GCP Jan 2024\r\n x_CostType, // GCP Jan 2024\r\n x_CurrencyConversionRate, // GCP Jun 2024\r\n x_CustomerId, // Azure 1.0-preview(v1)+\r\n x_CustomerName, // Azure 1.0-preview(v1)+\r\n x_Discount = parse_json(x_Discount), // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice, // Azure 1.0-preview(v1)+\r\n x_ExportTime, // GCP Jan 2024\r\n x_IngestionTime, // Hubs add-on\r\n x_InvoiceId = coalesce(InvoiceId, x_InvoiceId), // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId = case( // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId == '-2', '',\r\n x_InvoiceSectionId\r\n ),\r\n x_InvoiceSectionName = case( // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName == 'Unassigned', '',\r\n x_InvoiceSectionName\r\n ),\r\n x_ListCostInUsd, // Azure 1.0-preview(v1)+\r\n x_Location, // GCP Jan 2024\r\n x_Operation, // AWS 1.0\r\n x_PartnerCreditApplied, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency), // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription, // Azure 1.0-preview(v1)+\r\n x_Project, // GCP Jan 2024\r\n x_PublisherCategory, // Azure 1.0-preview(v1)+\r\n x_PublisherId, // Azure 1.0-preview(v1)+\r\n x_ResellerId, // Azure 1.0-preview(v1)+\r\n x_ResellerName, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName = tolower(x_ResourceGroupName), // Azure 1.0-preview(v1)+\r\n x_ResourceType, // Azure 1.0-preview(v1)+\r\n x_ServiceCode, // AWS 1.0\r\n x_ServiceId, // GCP Jan 2024\r\n x_ServicePeriodEnd, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart, // Azure 1.0-preview(v1)+\r\n x_SkuDescription, // Azure 1.0-preview(v1)+\r\n x_SkuDetails = parse_json(x_SkuDetails), // Azure 1.0-preview(v1)+\r\n x_SkuIsCreditEligible, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName = coalesce(SkuMeter, x_SkuMeterName), // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber, // Azure 1.0-preview(v1)+\r\n x_SkuRegion, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily, // Azure 1.0-preview(v1)+\r\n x_SkuTerm, // Azure 1.0-preview(v1)+\r\n x_SkuTier, // Azure 1.0-preview(v1)+\r\n x_SourceChanges, // Hubs add-on\r\n x_SourceName, // Hubs add-on\r\n x_SourceProvider, // Hubs add-on\r\n x_SourceType, // Hubs add-on\r\n x_SourceVersion, // Hubs add-on\r\n x_UsageType // AWS 1.0\r\n}\r\n\r\n// Costs_final_v1_0 table\r\n.create-merge table Costs_final_v1_0 (\r\n AvailabilityZone: string,\r\n BilledCost: decimal,\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingAccountType: string, // Azure 1.0-preview(v1)+\r\n BillingCurrency: string,\r\n BillingPeriodEnd: datetime,\r\n BillingPeriodStart: datetime,\r\n ChargeCategory: string,\r\n ChargeClass: string,\r\n ChargeDescription: string,\r\n ChargeFrequency: string,\r\n ChargePeriodEnd: datetime,\r\n ChargePeriodStart: datetime,\r\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview only\r\n CommitmentDiscountId: string,\r\n CommitmentDiscountName: string,\r\n CommitmentDiscountStatus: string,\r\n CommitmentDiscountType: string,\r\n ConsumedQuantity: decimal,\r\n ConsumedUnit: string,\r\n ContractedCost: decimal,\r\n ContractedUnitPrice: decimal,\r\n EffectiveCost: decimal,\r\n InvoiceIssuerName: string,\r\n ListCost: decimal,\r\n ListUnitPrice: decimal,\r\n PricingCategory: string,\r\n PricingQuantity: decimal,\r\n PricingUnit: string,\r\n ProviderName: string,\r\n PublisherName: string,\r\n RegionId: string,\r\n RegionName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n ServiceCategory: string,\r\n ServiceName: string,\r\n SkuId: string,\r\n SkuPriceId: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n SubAccountType: string,\r\n Tags: dynamic,\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_BilledCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: decimal, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: decimal, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_ContractedCostInUsd: decimal, // Azure 1.0+\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_Credits: dynamic, // GCP Jan 2024\r\n x_CostType: string, // GCP Jan 2024\r\n x_CurrencyConversionRate: decimal, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: dynamic, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: decimal, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_Operation: string, // AWS 1.0\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: decimal, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency: string, // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceChanges: string, // Hubs add-on\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Update policy for Costs_raw -> Costs_final_v1_0 table\r\n.alter table Costs_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Costs_raw\",\r\n \"Query\": \"Costs_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Actual costs |===================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use ActualCosts_transform_v1_2() instead.', folder='Costs')\r\nActualCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n // TODO: Transform actual costs to FOCUS 1.0 format\r\n ActualCosts_raw\r\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodStart = Date,\r\n ChargePeriodEnd = Date + 1d,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = '',\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory = '',\r\n SkuId = '',\r\n SkuMeter = '',\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = SubscriptionName,\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentName = '',\r\n x_ComponentType = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel = '',\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for ActualCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"ActualCosts_raw\",\r\n \"Query\": \"ActualCosts_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Amortized costs |================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use AmortizedCosts_transform_v1_2() instead.', folder='Costs')\r\nAmortizedCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n // TODO: Transform actual costs to FOCUS 1.0 format\r\n AmortizedCosts_raw\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodStart = Date,\r\n ChargePeriodEnd = Date + 1d,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = '',\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory = '',\r\n SkuId = '',\r\n SkuMeter = '',\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = SubscriptionName,\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentName = '',\r\n x_ComponentType = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel = '',\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for AmortizedCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"AmortizedCosts_raw\",\r\n \"Query\": \"AmortizedCosts_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All commitment discount usage transformed to FOCUS 1.0. This includes reservationdeatils_raw. Use CommitmentDiscountUsage_transform_v1_2() instead.', folder='Commitment discounts')\r\nCommitmentDiscountUsage_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n CommitmentDiscountUsage_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\r\n ReservedHours = todecimal(ReservedHours),\r\n TotalReservedQuantity = todecimal(TotalReservedQuantity),\r\n UsedHours = todecimal(UsedHours)\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Handle resource columns\r\n | extend ResourceId = tolower(InstanceId)\r\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\r\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\r\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\r\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\r\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\r\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, x_ServiceModel) on x_ResourceType\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n ChargePeriodEnd = UsageDate + 1d,\r\n ChargePeriodStart = UsageDate,\r\n CommitmentDiscountCategory = 'Usage',\r\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\r\n CommitmentDiscountType = 'Reservation',\r\n ConsumedQuantity = UsedHours,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\r\n x_CommitmentDiscountCommittedAmount = ReservedHours,\r\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\r\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\r\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\r\n x_CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\r\n x_IngestionTime = ingestion_time(),\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n // x_RowId = hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // CommitmentDiscountId,\r\n // ResourceId,\r\n // ChargePeriodStart\r\n // )),\r\n x_ServiceModel,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\r\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\r\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\r\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\r\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\r\n}\r\n\r\n// CommitmentDiscountUsage_final_v1_0 table\r\n.create-merge table CommitmentDiscountUsage_final_v1_0 (\r\n ChargePeriodEnd: datetime, // Hubs add-on\r\n ChargePeriodStart: datetime, // MS 2023-03-01\r\n CommitmentDiscountCategory: string, // Hubs add-on\r\n CommitmentDiscountId: string, // MS 2023-03-01\r\n CommitmentDiscountType: string, // Hubs add-on\r\n ConsumedQuantity: decimal, // MS 2023-03-01\r\n ProviderName: string, // Hubs add-on\r\n ResourceId: string, // MS 2023-03-01\r\n ResourceName: string, // Hubs add-on\r\n ResourceType: string, // Hubs add-on\r\n ServiceCategory: string, // Hubs add-on\r\n ServiceName: string, // Hubs add-on\r\n SubAccountId: string, // Hubs add-on\r\n x_CommitmentDiscountCommittedCount: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountCommittedAmount: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedRatio: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountQuantity: decimal, // MS 2023-03-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_ResourceGroupName: string, // Hubs add-on\r\n x_ResourceType: string, // Hubs add-on\r\n x_ServiceModel: string, // Hubs add-on\r\n x_SkuOrderId: string, // MS 2023-03-01\r\n x_SkuSize: string, // MS 2023-03-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string // Hubs add-on\r\n)\r\n\r\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_0 table\r\n.alter table CommitmentDiscountUsage_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"CommitmentDiscountUsage_raw\",\r\n \"Query\": \"CommitmentDiscountUsage_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All recommendations transformed to FOCUS 1.0. Use Recommendations_transform_v1_2() instead.', folder='Recommendations')\r\nRecommendations_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Recommendations_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n CostWithNoReservedInstances = todecimal(CostWithNoReservedInstances),\r\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\r\n NetSavings = todecimal(NetSavings),\r\n RecommendedQuantity = todecimal(RecommendedQuantity),\r\n RecommendedQuantityNormalized = todecimal(RecommendedQuantityNormalized),\r\n TotalCostWithReservedInstances = todecimal(TotalCostWithReservedInstances)\r\n //\r\n | extend x_IngestionTime = ingestion_time()\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Convert JSON cost columns to decimal\r\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\r\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\r\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\r\n //\r\n // Build recommendation details\r\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\r\n | extend x_RecommendationDetails = case(\r\n x_SourceType == 'ReservationRecommendations', bag_pack(\r\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\r\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\r\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\r\n 'CommitmentDiscountResourceType', ResourceType,\r\n 'CommitmentDiscountScope', Scope,\r\n 'LookbackPeriodDuration', case(\r\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\r\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\r\n ''\r\n ),\r\n 'LookbackPeriodStart', FirstUsageDate,\r\n 'RecommendedQuantity', RecommendedQuantity,\r\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\r\n 'RegionId', Location,\r\n 'RegionName', RegionName,\r\n 'SkuMeterId', MeterId,\r\n 'SkuPriceDetails', SkuProperties,\r\n 'SkuSize', coalesce(SKU, SkuName),\r\n 'SkuTerm', isoMonths(Term)\r\n ),\r\n dynamic({})\r\n )\r\n //\r\n // Sort columns and apply final transforms\r\n | extend x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d)\r\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\r\n | project\r\n ProviderName,\r\n SubAccountId = iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), ''),\r\n x_IngestionTime,\r\n x_EffectiveCostAfter = TotalCostWithReservedInstances,\r\n x_EffectiveCostBefore = CostWithNoReservedInstances,\r\n x_EffectiveCostSavings = NetSavings,\r\n x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d),\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n// Recommendations_final_v1_0 table\r\n.create-merge table Recommendations_final_v1_0 (\r\n ProviderName: string,\r\n SubAccountId: string,\r\n x_IngestionTime: datetime,\r\n x_EffectiveCostAfter: decimal,\r\n x_EffectiveCostBefore: decimal,\r\n x_EffectiveCostSavings: decimal,\r\n x_RecommendationDate: datetime,\r\n x_RecommendationDetails: dynamic,\r\n x_SourceName: string,\r\n x_SourceProvider: string,\r\n x_SourceType: string,\r\n x_SourceVersion: string\r\n)\r\n\r\n// Update policy for Recommendations_raw -> Recommendations_final_v1_0 table\r\n.alter table Recommendations_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Recommendations_raw\",\r\n \"Query\": \"Recommendations_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All transactions transformed to FOCUS 1.0. Use Transactions_transform_v1_2() instead.', folder='Transactions')\r\nTransactions_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Transactions_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n Amount = todecimal(Amount),\r\n MonetaryCommitment = todecimal(MonetaryCommitment),\r\n Overage = todecimal(Overage),\r\n Quantity = todecimal(Quantity)\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Handle BillingPeriodStart/End\r\n | extend BillingMonth = tostring(BillingMonth)\r\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\r\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n BilledCost = Amount,\r\n BillingAccountId = case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\r\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\r\n ''\r\n ),\r\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\r\n BillingCurrency = Currency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory = case(\r\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = case(\r\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\r\n EventType == 'Refund', 'Correction',\r\n ''\r\n ),\r\n ChargeDescription = Description,\r\n ChargeFrequency = case(\r\n BillingFrequency == 'OneTime', 'One-Time',\r\n BillingFrequency == 'Recurring', 'Recurring',\r\n BillingFrequency\r\n ),\r\n ChargePeriodStart = EventDate,\r\n PricingQuantity = Quantity,\r\n PricingUnit = 'Reservations',\r\n ProviderName,\r\n RegionId = Region,\r\n RegionName = Region,\r\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\r\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerEmail,\r\n x_CostCenter = CostCenter,\r\n x_InvoiceId = InvoiceId,\r\n x_InvoiceNumber = Invoice,\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\r\n x_IngestionTime = ingestion_time(),\r\n x_MonetaryCommitment = MonetaryCommitment,\r\n x_Overage = Overage,\r\n x_PurchasingBillingAccountId = PurchasingEnrollment,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuOrderName = ReservationOrderName,\r\n x_SkuSize = ArmSkuName,\r\n x_SkuTerm = isoMonths(Term),\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId = PurchasingSubscriptionGuid,\r\n x_TransactionType = EventType\r\n}\r\n\r\n// Transactions_final_v1_0 table\r\n.create-merge table Transactions_final_v1_0 (\r\n BilledCost: decimal, // MS CM EA+MCA 2023-05-01\r\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\r\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\r\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n ChargeCategory: string, // Hubs add-on\r\n ChargeClass: string, // Hubs add-on\r\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\r\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\r\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n PricingQuantity: decimal, // MS CM EA+MCA 2023-05-01\r\n PricingUnit: string, // Hubs add-on\r\n ProviderName: string, // Hubs add-on\r\n RegionId: string, // MS CM EA+MCA 2023-05-01\r\n RegionName: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\r\n x_AccountName: string, // MS CM EA 2023-05-01\r\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\r\n x_CostCenter: string, // MS CM EA 2023-05-01\r\n x_InvoiceId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_MonetaryCommitment: decimal, // MS CM EA 2023-05-01\r\n x_Overage: decimal, // MS CM EA 2023-05-01\r\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\r\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\r\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\r\n)\r\n\r\n// Update policy for Transactions_raw -> Transactions_final_v1_0 table\r\n.alter table Transactions_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Transactions_raw\",\r\n \"Query\": \"Transactions_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n", - "dataExplorerPrivateDnsZoneName": "[replace(format('privatelink.{0}.{1}', parameters('location'), replace(environment().suffixes.storage, 'core', 'kusto')), '..', '.')]", - "ingestionCapacity": { - "Dev(No SLA)_Standard_E2a_v4": 1, - "Dev(No SLA)_Standard_D11_v2": 1, - "Standard_D11_v2": 2, - "Standard_D12_v2": 4, - "Standard_D13_v2": 8, - "Standard_D14_v2": 16, - "Standard_D16d_v5": 16, - "Standard_D32d_v4": 32, - "Standard_D32d_v5": 32, - "Standard_DS13_v2+1TB_PS": 8, - "Standard_DS13_v2+2TB_PS": 8, - "Standard_DS14_v2+3TB_PS": 16, - "Standard_DS14_v2+4TB_PS": 16, - "Standard_E2a_v4": 2, - "Standard_E2ads_v5": 2, - "Standard_E2d_v4": 2, - "Standard_E2d_v5": 2, - "Standard_E4a_v4": 4, - "Standard_E4ads_v5": 4, - "Standard_E4d_v4": 4, - "Standard_E4d_v5": 4, - "Standard_E8a_v4": 8, - "Standard_E8ads_v5": 8, - "Standard_E8as_v4+1TB_PS": 8, - "Standard_E8as_v4+2TB_PS": 8, - "Standard_E8as_v5+1TB_PS": 8, - "Standard_E8as_v5+2TB_PS": 8, - "Standard_E8d_v4": 8, - "Standard_E8d_v5": 8, - "Standard_E8s_v4+1TB_PS": 8, - "Standard_E8s_v4+2TB_PS": 8, - "Standard_E8s_v5+1TB_PS": 8, - "Standard_E8s_v5+2TB_PS": 8, - "Standard_E16a_v4": 16, - "Standard_E16ads_v5": 16, - "Standard_E16as_v4+3TB_PS": 16, - "Standard_E16as_v4+4TB_PS": 16, - "Standard_E16as_v5+3TB_PS": 16, - "Standard_E16as_v5+4TB_PS": 16, - "Standard_E16d_v4": 16, - "Standard_E16d_v5": 16, - "Standard_E16s_v4+3TB_PS": 16, - "Standard_E16s_v4+4TB_PS": 16, - "Standard_E16s_v5+3TB_PS": 16, - "Standard_E16s_v5+4TB_PS": 16, - "Standard_E64i_v3": 64, - "Standard_E80ids_v4": 80, - "Standard_EC8ads_v5": 8, - "Standard_EC8as_v5+1TB_PS": 8, - "Standard_EC8as_v5+2TB_PS": 8, - "Standard_EC16ads_v5": 16, - "Standard_EC16as_v5+3TB_PS": 16, - "Standard_EC16as_v5+4TB_PS": 16, - "Standard_L4s": 4, - "Standard_L8as_v3": 8, - "Standard_L8s": 8, - "Standard_L8s_v2": 8, - "Standard_L8s_v3": 8, - "Standard_L16as_v3": 16, - "Standard_L16s": 16, - "Standard_L16s_v2": 16, - "Standard_L16s_v3": 16, - "Standard_L32as_v3": 32, - "Standard_L32s_v3": 32 - } + "$fxv#0": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\"}\n },\n {\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\"}\n },\n {\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerId\"}\n },\n {\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionId\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionName\"}\n },\n {\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"Date\"}\n },\n {\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\"}\n },\n {\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\"}\n },\n {\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\"}\n },\n {\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\"}\n },\n {\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\"}\n },\n {\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\"}\n },\n {\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\"}\n },\n {\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectivePrice\"}\n },\n {\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Cost\"}\n },\n {\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\"}\n },\n {\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\"}\n },\n {\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceLocation\"}\n },\n {\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\"}\n },\n {\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedService\"}\n },\n {\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\"}\n },\n {\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo1\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo2\"}\n },\n {\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AdditionalInfo\"}\n },\n {\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSection\"}\n },\n {\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\"}\n },\n {\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\"}\n },\n {\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceGroup\"}\n },\n {\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\"}\n },\n {\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationName\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderId\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderName\"}\n },\n {\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferId\"}\n },\n {\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\n },\n {\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\"}\n },\n {\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\"}\n },\n {\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PlanName\"}\n },\n {\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeType\"}\n },\n {\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Frequency\"}\n },\n {\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherType\"}\n }\n ]\n }\n}\n", + "$fxv#1": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\"}\n },\n {\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\"}\n },\n {\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerId\"}\n },\n {\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionId\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionName\"}\n },\n {\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"Date\"}\n },\n {\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\"}\n },\n {\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\"}\n },\n {\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\"}\n },\n {\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\"}\n },\n {\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\"}\n },\n {\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\"}\n },\n {\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\"}\n },\n {\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectivePrice\"}\n },\n {\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Cost\"}\n },\n {\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\"}\n },\n {\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\"}\n },\n {\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceLocation\"}\n },\n {\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\"}\n },\n {\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedService\"}\n },\n {\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\"}\n },\n {\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo1\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo2\"}\n },\n {\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AdditionalInfo\"}\n },\n {\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSection\"}\n },\n {\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\"}\n },\n {\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\"}\n },\n {\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceGroup\"}\n },\n {\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\"}\n },\n {\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationName\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderId\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderName\"}\n },\n {\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferId\"}\n },\n {\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\n },\n {\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\"}\n },\n {\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\"}\n },\n {\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PlanName\"}\n },\n {\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeType\"}\n },\n {\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Frequency\"}\n },\n {\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherType\"}\n }\n ]\n }\n}\n", + "$fxv#10": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"SKU\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SKU\" }\n },\n {\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Location\" }\n },\n {\n \"source\": { \"name\": \"CostWithNoReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostWithNoReservedInstances\" }\n },\n {\n \"source\": { \"name\": \"FirstUsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"FirstUsageDate\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\n \"sink\": { \"name\": \"LookBackPeriod\" }\n },\n {\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"NetSavings\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"NetSavings\" }\n },\n {\n \"source\": { \"name\": \"NormalizedSize\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NormalizedSize\" }\n },\n {\n \"source\": { \"name\": \"RecommendedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantity\" }\n },\n {\n \"source\": { \"name\": \"RecommendedQuantityNormalized\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"Scope\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Scope\" }\n },\n {\n \"source\": { \"name\": \"SkuProperties\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuProperties\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"TotalCostWithReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TotalCostWithReservedInstances\" }\n }\n ]\n }\n}\n", + "$fxv#11": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"Cost With No ReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostWithNoReservedInstancesJson\" }\n },\n {\n \"source\": { \"name\": \"First UsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"FirstUsageDate\" }\n },\n {\n \"source\": { \"name\": \"Instance Flexibility Ratio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"Instance Flexibility Group\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Location\" }\n },\n {\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\n \"sink\": { \"name\": \"LookBackPeriod\" }\n },\n {\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"Net Savings\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NetSavingsJson\" }\n },\n {\n \"source\": { \"name\": \"Normalized Size\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NormalizedSize\" }\n },\n {\n \"source\": { \"name\": \"Recommended Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantity\" }\n },\n {\n \"source\": { \"name\": \"Recommended Quantity Normalized\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"scope\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Scope\" }\n },\n {\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuName\" }\n },\n {\n \"source\": { \"name\": \"Sku Properties\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuProperties\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"Total Cost With ReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TotalCostWithReservedInstancesJson\" }\n }\n ]\n }\n}\n", + "$fxv#12": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\" }\n },\n {\n \"source\": { \"name\": \"AccountOwnerEmail\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerEmail\" }\n },\n {\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Amount\" }\n },\n {\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ArmSkuName\" }\n },\n {\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingFrequency\" }\n },\n {\n \"source\": { \"name\": \"BillingMonth\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingMonth\" }\n },\n {\n \"source\": { \"name\": \"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"CurrentEnrollmentId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CurrentEnrollmentId\" }\n },\n {\n \"source\": { \"name\": \"DepartmentName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"DepartmentName\" }\n },\n {\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Description\" }\n },\n {\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EventDate\" }\n },\n {\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EventType\" }\n },\n {\n \"source\": { \"name\": \"MonetaryCommitment\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MonetaryCommitment\" }\n },\n {\n \"source\": { \"name\": \"Overage\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Overage\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\n },\n {\n \"source\": { \"name\": \"PurchasingEnrollment\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingEnrollment\" }\n },\n {\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderName\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n }\n ]\n }\n}\n", + "$fxv#13": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Amount\" }\n },\n {\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ArmSkuName\" }\n },\n {\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingFrequency\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Description\" }\n },\n {\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EventDate\" }\n },\n {\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EventType\" }\n },\n {\n \"source\": { \"name\": \"Invoice\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Invoice\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\n },\n {\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderName\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n }\n ]\n }\n}\n", + "$fxv#2": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationId\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceSubcategory\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuMeter\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceDetails\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AmortizationClass\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ServiceModel\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPlanName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", + "$fxv#3": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationId\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceSubcategory\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuMeter\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceDetails\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AmortizationClass\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ServiceModel\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPlanName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", + "$fxv#4": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", + "$fxv#5": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", + "$fxv#6": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\" }\n },\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeSubcategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"UsageQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UsageQuantity\" }\n },\n {\n \"source\": { \"name\": \"UsageUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UsageUnit\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ChargeId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ChargeId\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandCost\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", + "$fxv#7": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"EnrollmentNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EnrollmentNumber\" }\n },\n {\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterID\" }\n },\n {\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\" }\n },\n {\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterType\" }\n },\n {\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\" }\n },\n {\n \"source\": { \"name\": \"SkuID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuID\" }\n },\n {\n \"source\": { \"name\": \"ProductID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductID\" }\n },\n {\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\" }\n },\n {\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\" }\n },\n {\n \"source\": { \"name\": \"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\" }\n },\n {\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveStartDate\" }\n },\n {\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveEndDate\" }\n },\n {\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\" }\n },\n {\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BasePrice\" }\n },\n {\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MarketPrice\" }\n },\n {\n \"source\": { \"name\": \"CurrencyCode\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CurrencyCode\" }\n },\n {\n \"source\": { \"name\": \"IncludedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"IncludedQuantity\" }\n },\n {\n \"source\": { \"name\": \"OfferID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferID\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PriceType\" }\n }\n ]\n }\n}\n", + "$fxv#8": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\" }\n },\n {\n \"source\": { \"name\": \"ProductId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductId\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\" }\n },\n {\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\" }\n },\n {\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterType\" }\n },\n {\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\" }\n },\n {\n \"source\": { \"name\": \"TierMinimumUnits\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TierMinimumUnits\" }\n },\n {\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveStartDate\" }\n },\n {\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveEndDate\" }\n },\n {\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\" }\n },\n {\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BasePrice\" }\n },\n {\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MarketPrice\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PriceType\" }\n }\n ]\n }\n}\n", + "$fxv#9": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"InstanceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceId\" }\n },\n {\n \"source\": { \"name\": \"Kind\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Kind\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\" }\n },\n {\n \"source\": { \"name\": \"ReservedHours\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ReservedHours\" }\n },\n {\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuName\" }\n },\n {\n \"source\": { \"name\": \"TotalReservedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"TotalReservedQuantity\" }\n },\n {\n \"source\": { \"name\": \"UsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"UsageDate\" }\n },\n {\n \"source\": { \"name\": \"UsedHours\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UsedHours\" }\n }\n ]\n }\n}\n", + "CONFIG": "config", + "INGESTION": "ingestion", + "MSEXPORTS": "msexports", + "ingestionIdFileNameSeparator": "__", + "finOpsToolkitVersion": "12.0" }, - "resources": [ - { - "type": "Microsoft.Kusto/clusters/principalAssignments", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('clusterName'), 'adf-mi-cluster-admin')]", - "properties": { - "principalType": "App", - "principalId": "[reference(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), '2018-06-01', 'full').identity.principalId]", - "tenantId": "[reference(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), '2018-06-01', 'full').identity.tenantId]", - "role": "AllDatabasesAdmin" - }, + "resources": { + "dataFactory::linkedService_storageAccount": { + "existing": true, + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + "appRegistration" ] }, - { - "type": "Microsoft.Kusto/clusters/databases", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('clusterName'), 'Ingestion')]", - "location": "[parameters('location')]", - "kind": "ReadWrite", + "dataFactory::dataset_config": { + "existing": true, + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]", "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + "appRegistration" ] }, - { - "type": "Microsoft.Kusto/clusters/databases", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('clusterName'), 'Hub')]", - "location": "[parameters('location')]", - "kind": "ReadWrite", + "dataFactory::dataset_ingestion": { + "existing": true, + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('INGESTION'))]", "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + "appRegistration" ] }, - { - "type": "Microsoft.Kusto/clusters", - "apiVersion": "2023-08-15", - "name": "[parameters('clusterName')]", - "location": "[parameters('location')]", - "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Kusto/clusters'), createObject()))]", - "sku": { - "name": "[parameters('clusterSku')]", - "tier": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 'Basic', 'Standard')]", - "capacity": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 1, if(equals(parameters('clusterCapacity'), 1), 2, parameters('clusterCapacity')))]" - }, - "identity": { - "type": "SystemAssigned" + "dataFactory::dataset_ingestion_files": { + "existing": true, + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', variables('INGESTION')))]", + "dependsOn": [ + "appRegistration" + ] + }, + "dataFactory::dataset_manifest": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'manifest')]", + "properties": { + "parameters": { + "fileName": { + "type": "String", + "defaultValue": "manifest.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[variables('MSEXPORTS')]" + } + }, + "type": "Json", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().fileName}", + "type": "Expression" + }, + "folderPath": { + "value": "@{dataset().folderPath}", + "type": "Expression" + } + } + }, + "linkedServiceName": { + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + } }, + "dependsOn": [ + "appRegistration" + ] + }, + "dataFactory::dataset_msexports": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, replace(format('{0}', variables('MSEXPORTS')), '-', '_'))]", "properties": { - "enableStreamingIngest": true, - "enableAutoStop": false, - "publicNetworkAccess": "[if(parameters('enablePublicAccess'), 'Enabled', 'Disabled')]" - } + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" + }, + "fileSystem": "[reference('exportContainer').outputs.containerName.value]" + }, + "columnDelimiter": ",", + "escapeChar": "\"", + "quoteChar": "\"", + "firstRowAsHeader": true + }, + "linkedServiceName": { + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "appRegistration", + "exportContainer" + ] }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", - "name": "[guid(parameters('clusterName'), subscription().id, 'Storage Blob Data Contributor')]", + "dataFactory::dataset_msexports_gzip": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_gzip', variables('MSEXPORTS')))]", "properties": { - "description": "Give \"Storage Blob Data Contributor\" to the cluster", - "principalId": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15', 'full').identity.principalId]", - "principalType": "ServicePrincipal", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" + }, + "fileSystem": "[variables('MSEXPORTS')]" + }, + "columnDelimiter": ",", + "escapeChar": "\"", + "quoteChar": "\"", + "firstRowAsHeader": true, + "compressionCodec": "Gzip" + }, + "linkedServiceName": { + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + } }, "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + "appRegistration" ] }, - { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[variables('dataExplorerPrivateDnsZoneName')]", - "location": "global", - "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateDnsZones'), createObject()))]", - "properties": {} - }, - { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('dataExplorerPrivateDnsZoneName'), format('{0}-link', replace(variables('dataExplorerPrivateDnsZoneName'), '.', '-')))]", - "location": "global", - "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks'), createObject()))]", + "dataFactory::dataset_msexports_parquet": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_parquet', variables('MSEXPORTS')))]", "properties": { - "virtualNetwork": { - "id": "[parameters('virtualNetworkId')]" + "parameters": { + "blobPath": { + "type": "String" + } }, - "registrationEnabled": false + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" + }, + "fileSystem": "[variables('MSEXPORTS')]" + } + }, + "linkedServiceName": { + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + } }, "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" + "appRegistration" ] }, - { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('clusterName'))]", - "location": "[parameters('location')]", - "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateEndpoints'), createObject()))]", + "dataFactory::pipeline_ExecuteExportsETL": { + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ExecuteETL', variables('MSEXPORTS')))]", "properties": { - "subnet": { - "id": "[parameters('privateEndpointSubnetId')]" - }, - "privateLinkServiceConnections": [ + "activities": [ + { + "name": "Wait", + "description": "Files may not be available immediately after being created.", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 60 + } + }, + { + "name": "Read Manifest", + "description": "Load the export manifest to determine the scope, dataset, and date range.", + "type": "Lookup", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Completed" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@pipeline().parameters.fileName", + "type": "Expression" + }, + "folderPath": { + "value": "@pipeline().parameters.folderPath", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Has No Rows", + "description": "Check the row count ", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "hasNoRows", + "value": { + "value": "@or(equals(activity('Read Manifest').output.firstRow.blobCount, null), equals(activity('Read Manifest').output.firstRow.blobCount, 0))", + "type": "Expression" + } + } + }, + { + "name": "Set Export Dataset Type", + "description": "Save the dataset type from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "exportDatasetType", + "value": { + "value": "@activity('Read Manifest').output.firstRow.exportConfig.type", + "type": "Expression" + } + } + }, + { + "name": "Set MCA Column", + "description": "Determines if the dataset schema has channel-specific columns and saves the column name that only exists in MCA to determine if it is an MCA dataset.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "mcaColumnToCheck", + "value": { + "value": "@if(contains(createArray('pricesheet', 'reservationtransactions'), toLower(variables('exportDatasetType'))), 'BillingProfileId', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Net Savings', null))", + "type": "Expression" + } + } + }, + { + "name": "Set Export Dataset Version", + "description": "Save the dataset version from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "exportDatasetVersion", + "value": { + "value": "@activity('Read Manifest').output.firstRow.exportConfig.dataVersion", + "type": "Expression" + } + } + }, + { + "name": "Detect Channel", + "description": "Determines what channel this export is from. Switch statement handles the different file types if the mcaColumnToCheck variable is set.", + "type": "Switch", + "dependsOn": [ + { + "activity": "Set Has No Rows", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set MCA Column", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Export Dataset Version", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "on": { + "value": "@if(or(empty(variables('mcaColumnToCheck')), variables('hasNoRows')), 'ignore', last(array(split(activity('Read Manifest').output.firstRow.blobs[0].blobName, '.'))))", + "type": "Expression" + }, + "cases": [ + { + "value": "csv", + "activities": [ + { + "name": "Check for MCA Column in CSV", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "dataset": { + "referenceName": "[replace(format('{0}', variables('MSEXPORTS')), '-', '_')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel in CSV", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in CSV", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in CSV').output, 'firstRow'), contains(activity('Check for MCA Column in CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + }, + { + "value": "gz", + "activities": [ + { + "name": "Check for MCA Column in Gzip CSV", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "dataset": { + "referenceName": "[format('{0}_gzip', variables('MSEXPORTS'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel in Gzip CSV", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in Gzip CSV", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Gzip CSV').output, 'firstRow'), contains(activity('Check for MCA Column in Gzip CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + }, + { + "value": "parquet", + "activities": [ + { + "name": "Check for MCA Column in Parquet", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "ParquetSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + }, + "dataset": { + "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel for Parquet", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in Parquet", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Parquet').output, 'firstRow'), contains(activity('Check for MCA Column in Parquet').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + } + ], + "defaultActivities": [ + { + "name": "Set Schema File", + "type": "SetVariable", + "dependsOn": [], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), '.json'))", + "type": "Expression" + } + } + } + ] + } + }, + { + "name": "Set Scope", + "description": "Save the scope from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "scope", + "value": { + "value": "@split(toLower(activity('Read Manifest').output.firstRow.exportConfig.resourceId), '/providers/microsoft.costmanagement/exports/')[0]", + "type": "Expression" + } + } + }, + { + "name": "Set Date", + "description": "Save the exported month from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "date", + "value": { + "value": "@replace(substring(activity('Read Manifest').output.firstRow.runInfo.startDate, 0, 7), '-', '')", + "type": "Expression" + } + } + }, { - "name": "dataExplorerLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "groupIds": [ - "cluster" + "name": "Failed to Read Manifest", + "type": "Fail", + "dependsOn": [ + { + "activity": "Set Date", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Scope", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Export Dataset Version", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Detect Channel", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Failed to read the manifest file for this export run. Manifest path: ', pipeline().parameters.folderPath)", + "type": "Expression" + }, + "errorCode": "ManifestReadFailed" + } + }, + { + "name": "Check Schema", + "description": "Verify that the schema file exists in storage.", + "type": "GetMetadata", + "dependsOn": [ + { + "activity": "Set Scope", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Date", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Detect Channel", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[variables('CONFIG')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('schemaFile')", + "type": "Expression" + }, + "folderPath": "[format('{0}/schemas', reference('schemaFiles').outputs.containerName.value)]" + } + }, + "fieldList": [ + "exists" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + } + }, + { + "name": "Schema Not Found", + "type": "Fail", + "dependsOn": [ + { + "activity": "Check Schema", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('The ', variables('schemaFile'), ' schema mapping file was not found. Please confirm version ', variables('exportDatasetVersion'), ' of the ', variables('exportDatasetType'), ' dataset is supported by this version of FinOps hubs. You may need to upgrade to a newer release. To add support for another dataset, you can create a custom mapping file.')", + "type": "Expression" + }, + "errorCode": "SchemaNotFound" + } + }, + { + "name": "Set Hub Dataset", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "hubDataset", + "value": { + "value": "@if(equals(toLower(variables('exportDatasetType')), 'focuscost'), 'Costs', if(equals(toLower(variables('exportDatasetType')), 'pricesheet'), 'Prices', if(equals(toLower(variables('exportDatasetType')), 'reservationdetails'), 'CommitmentDiscountUsage', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Recommendations', if(equals(toLower(variables('exportDatasetType')), 'reservationtransactions'), 'Transactions', if(equals(toLower(variables('exportDatasetType')), 'actualcost'), 'ActualCosts', if(equals(toLower(variables('exportDatasetType')), 'amortizedcost'), 'AmortizedCosts', toLower(variables('exportDatasetType')))))))))", + "type": "Expression" + } + } + }, + { + "name": "Set Destination Folder", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check Schema", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Hub Dataset", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "destinationFolder", + "value": { + "value": "@replace(concat(variables('hubDataset'),'/',substring(variables('date'), 0, 4),'/',substring(variables('date'), 4, 2),'/',toLower(variables('scope')), if(equals(variables('hubDataset'), 'Recommendations'), activity('Read Manifest').output.firstRow.exportConfig.exportName, '')),'//','/')", + "type": "Expression" + } + } + }, + { + "name": "For Each Blob", + "description": "Loop thru each exported file listed in the manifest.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Set Destination Folder", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(variables('hasNoRows'), json('[]'), activity('Read Manifest').output.firstRow.blobs)", + "type": "Expression" + }, + "batchCount": "[if(parameters('app').hub.options.privateRouting, 4, 30)]", + "isSequential": false, + "activities": [ + { + "name": "Execute", + "description": "Run the ingestion ETL pipeline.", + "type": "ExecutePipeline", + "dependsOn": [], + "policy": { + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_ETL_{1}', variables('MSEXPORTS'), variables('INGESTION'))]", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "blobPath": { + "value": "@item().blobName", + "type": "Expression" + }, + "destinationFolder": { + "value": "@variables('destinationFolder')", + "type": "Expression" + }, + "destinationFile": { + "value": "@last(array(split(replace(replace(item().blobName, '.gz', ''), '.csv', '.parquet'), '/')))", + "type": "Expression" + }, + "ingestionId": { + "value": "@activity('Read Manifest').output.firstRow.runInfo.runId", + "type": "Expression" + }, + "schemaFile": { + "value": "@variables('schemaFile')", + "type": "Expression" + }, + "exportDatasetType": { + "value": "@variables('exportDatasetType')", + "type": "Expression" + }, + "exportDatasetVersion": { + "value": "@variables('exportDatasetVersion')", + "type": "Expression" + } + } + } + } ] } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" - ] - }, - { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('clusterName')), 'dataExplorer-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "privatelink-westus-kusto-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" - } - }, - { - "name": "privatelink-blob-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } }, { - "name": "privatelink-table-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.table.{0}', environment().suffixes.storage))]" - } + "name": "Copy Manifest", + "description": "Copy the manifest to the ingestion container to trigger ADX ingestion", + "type": "Copy", + "dependsOn": [ + { + "activity": "For Each Blob", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "sink": { + "type": "JsonSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "JsonWriteSettings" + } + }, + "enableStaging": false + }, + "inputs": [ + { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": "manifest.json", + "folderPath": { + "value": "@pipeline().parameters.folderPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": "manifest.json", + "folderPath": { + "value": "[format('@concat(''{0}/'', variables(''destinationFolder''))', variables('INGESTION'))]", + "type": "Expression" + } + } + } + ] + } + ], + "parameters": { + "folderPath": { + "type": "string" }, - { - "name": "privatelink-queue-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" - } + "fileName": { + "type": "string" } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-ep', parameters('clusterName')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ingestion_OpenDataInternalScripts", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" + "variables": { + "date": { + "type": "String" }, - "databaseName": { - "value": "Ingestion" + "destinationFolder": { + "type": "String" }, - "scripts": { - "value": { - "OpenDataFunctions_resource_type_1": "[variables('$fxv#0')]", - "OpenDataFunctions_resource_type_2": "[variables('$fxv#1')]", - "OpenDataFunctions_resource_type_3": "[variables('$fxv#2')]", - "OpenDataFunctions_resource_type_4": "[variables('$fxv#3')]", - "OpenDataFunctions_resource_type_5": "[variables('$fxv#4')]" - } + "exportDatasetType": { + "type": "String" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "exportDatasetVersion": { + "type": "String" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "hasNoRows": { + "type": "Boolean" + }, + "hubDataset": { + "type": "String" + }, + "mcaColumnToCheck": { + "type": "String" + }, + "schemaFile": { + "type": "String" + }, + "scope": { + "type": "String" } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" - } - }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } + "annotations": [ + "New export" + ] + }, + "dependsOn": [ + "appRegistration", + "dataFactory::dataset_manifest", + "dataFactory::dataset_msexports", + "dataFactory::dataset_msexports_gzip", + "dataFactory::dataset_msexports_parquet", + "dataFactory::pipeline_ToIngestion", + "schemaFiles" + ] + }, + "dataFactory::pipeline_ToIngestion": { + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ETL_{1}', variables('MSEXPORTS'), variables('INGESTION')))]", + "properties": { + "activities": [ + { + "name": "Get Existing Parquet Files", + "description": "Get the previously ingested files so we can remove any older data. This is necessary to avoid data duplication in reports.", + "type": "GetMetadata", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[format('{0}_files', variables('INGESTION'))]", + "type": "DatasetReference", + "parameters": { + "folderPath": "@pipeline().parameters.destinationFolder" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + { + "name": "Filter Out Current Exports", + "description": "Remove existing files from the current export so those files do not get deleted.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Get Existing Parquet Files", + "dependencyConditions": [ + "Completed" + ] } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", + "type": "Expression" + }, + "condition": { + "value": "[format('@and(endswith(item().name, ''.parquet''), not(startswith(item().name, concat(pipeline().parameters.ingestionId, ''{0}''))))', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "Load Schema Mappings", + "description": "Get schema mapping file to use for the CSV to parquet conversion.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + "dataset": { + "referenceName": "[variables('CONFIG')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@toLower(pipeline().parameters.schemaFile)", + "type": "Expression" + }, + "folderPath": "[format('{0}/schemas', variables('CONFIG'))]" + } } } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ingestion_InitScripts", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" - }, - "databaseName": { - "value": "Ingestion" - }, - "scripts": { - "value": { - "openData": "[variables('$fxv#5')]", - "common": "[variables('$fxv#6')]", - "infra": "[variables('$fxv#7')]", - "rawTables": "[replace(variables('$fxv#8'), '$$rawRetentionInDays$$', string(parameters('rawRetentionInDays')))]" - } - }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" + { + "name": "Failed to Load Schema", + "type": "Fail", + "dependsOn": [ + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to load the ', pipeline().parameters.schemaFile, ' schema file. Please confirm the schema and version are supported for FinOps hubs ingestion. Unsupported files will remain in the msexports container.')", + "type": "Expression" + }, + "errorCode": "SchemaLoadFailed" } }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + { + "name": "Set Additional Columns", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "userProperties": [], + "typeProperties": { + "variableName": "additionalColumns", + "value": { + "value": "@intersection(array(json(concat('[{\"name\":\"x_SourceProvider\",\"value\":\"Microsoft\"},{\"name\":\"x_SourceName\",\"value\":\"Cost Management\"},{\"name\":\"x_SourceType\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"},{\"name\":\"x_SourceVersion\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"}'))), activity('Load Schema Mappings').output.firstRow.additionalColumns)", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "For Each Old File", + "description": "Loop thru each of the existing files from previous exports.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Convert to Parquet", + "dependencyConditions": [ + "Succeeded" + ] }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "activity": "Filter Out Current Exports", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Filter Out Current Exports').output.Value", + "type": "Expression" + }, + "activities": [ + { + "name": "Delete Old Ingested File", + "description": "Delete the previously ingested files from older exports.", + "type": "Delete", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[variables('INGESTION')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@concat(pipeline().parameters.destinationFolder, '/', item().name)", + "type": "Expression" + } + } + }, + "enableLogging": false, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + } + } + } + ] } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]", - "[resourceId('Microsoft.Resources/deployments', 'ingestion_OpenDataInternalScripts')]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ingestion_VersionedScripts", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" }, - "databaseName": { - "value": "Ingestion" - }, - "scripts": { - "value": { - "v1_0": "[variables('$fxv#9')]", - "v1_2": "[variables('$fxv#10')]" + { + "name": "Set Destination Path", + "type": "SetVariable", + "dependsOn": [], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "destinationPath", + "value": { + "value": "[format('@concat(pipeline().parameters.destinationFolder, ''/'', pipeline().parameters.ingestionId, ''{0}'', pipeline().parameters.destinationFile)', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" + } } }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" - }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" + { + "name": "Convert to Parquet", + "description": "[format('Convert CSV to parquet and move the file to the {0} container.', variables('INGESTION'))]", + "type": "Switch", + "dependsOn": [ + { + "activity": "Set Destination Path", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Additional Columns", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "on": { + "value": "@last(array(split(pipeline().parameters.blobPath, '.')))", + "type": "Expression" + }, + "cases": [ + { + "value": "csv", + "activities": [ + { + "name": "Convert CSV File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:10:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false, + "translator": { + "value": "@activity('Load Schema Mappings').output.firstRow.translator", + "type": "Expression" + } + }, + "inputs": [ + { + "referenceName": "[replace(format('{0}', variables('MSEXPORTS')), '-', '_')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('INGESTION')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] + } + ] + }, + { + "value": "gz", + "activities": [ + { + "name": "Convert GZip CSV File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:10:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false, + "translator": { + "value": "@activity('Load Schema Mappings').output.firstRow.translator", + "type": "Expression" + } + }, + "inputs": [ + { + "referenceName": "[format('{0}_gzip', variables('MSEXPORTS'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('INGESTION')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] + } + ] + }, + { + "value": "parquet", + "activities": [ + { + "name": "Move Parquet File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "ParquetSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false + }, + "inputs": [ + { + "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('INGESTION')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] + } + ] + } + ], + "defaultActivities": [ + { + "name": "Unsupported File Type", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to ingest the specified export file because the file type is not supported. File: ', pipeline().parameters.blobPath)", + "type": "Expression" + }, + "errorCode": "UnsupportedExportFileType" + } + } + ] } }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." - } + { + "name": "Read Hub Config", + "description": "Read the hub config to determine if the export should be retained.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('CONFIG')]", + "type": "DatasetReference", + "parameters": { + "fileName": "settings.json", + "folderPath": "[variables('CONFIG')]" + } } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "If Not Retaining Exports", + "description": "If the msexports retention period <= 0, delete the source file. The main reason to keep the source file is to allow for troubleshooting and reprocessing in the future.", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Convert to Parquet", + "dependencyConditions": [ + "Succeeded" + ] }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "activity": "Read Hub Config", + "dependencyConditions": [ + "Completed" + ] } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@lessOrEquals(coalesce(activity('Read Hub Config').output.firstRow.retention.msexports.days, 0), 0)", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Delete Source File", + "description": "Delete the exported data file to keep storage costs down. This file is not referenced by any reporting systems.", + "type": "Delete", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + }, + "enableLogging": false, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + } + } + } + ] } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]", - "[resourceId('Microsoft.Resources/deployments', 'ingestion_InitScripts')]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "hub_InitScripts", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", + } + ], "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" + "blobPath": { + "type": "String" }, - "databaseName": { - "value": "Hub" + "destinationFile": { + "type": "string" }, - "scripts": { - "value": { - "common": "[variables('$fxv#11')]", - "openData": "[variables('$fxv#12')]" - } + "destinationFolder": { + "type": "string" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "ingestionId": { + "type": "string" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" - } + "schemaFile": { + "type": "string" }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." - } - } + "exportDatasetType": { + "type": "string" }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" - } - } - ] + "exportDatasetVersion": { + "type": "string" + } + }, + "variables": { + "additionalColumns": { + "type": "Array" + }, + "destinationPath": { + "type": "String" + } } }, "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", - "[resourceId('Microsoft.Resources/deployments', 'ingestion_InitScripts')]" + "appRegistration", + "dataFactory::dataset_msexports", + "dataFactory::dataset_msexports_gzip", + "dataFactory::dataset_msexports_parquet" ] }, - { + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "dependsOn": [ + "appRegistration" + ] + }, + "appRegistration": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "hub_VersionedScripts", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.Exports_Register", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" - }, - "databaseName": { - "value": "Hub" + "app": { + "value": "[parameters('app')]" }, - "scripts": { - "value": { - "v1_0": "[variables('$fxv#13')]", - "v1_2": "[variables('$fxv#14')]" - } + "version": { + "value": "[variables('finOpsToolkitVersion')]" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "features": { + "value": [ + "Storage", + "DataFactory" + ] }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "storageRoles": { + "value": [ + "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9" + ] } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" + "version": "0.39.26.7824", + "templateHash": "5436870138046688593" } }, - "parameters": { - "clusterName": { - "type": "string", + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "databaseName": { - "type": "string", + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "scripts": { + "_1.IdNameObject": { "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } } }, - "resources": [ + "functions": [ { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } } } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", - "[resourceId('Microsoft.Resources/deployments', 'hub_InitScripts')]", - "[resourceId('Microsoft.Resources/deployments', 'ingestion_VersionedScripts')]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "hub_LatestScripts", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" - }, - "databaseName": { - "value": "Hub" - }, - "scripts": { - "value": { - "latest": "[variables('$fxv#15')]" - } - }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" - }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" - } - }, + ], "parameters": { - "clusterName": { - "type": "string", + "app": { + "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." + "description": "Required. FinOps hub app getting deployed." } }, - "databaseName": { + "version": { "type": "string", "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + "description": "Required. Version number of the FinOps hub app." } }, - "scripts": { - "type": "object", + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." } }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, + "storageRoles": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." } }, - "forceUpdateTag": { + "telemetryString": { "type": "string", - "defaultValue": "[utcNow()]", + "defaultValue": "", "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", - "[resourceId('Microsoft.Resources/deployments', 'hub_VersionedScripts')]" - ] - } - ], - "outputs": { - "clusterId": { - "type": "string", - "metadata": { - "description": "The resource ID of the cluster." - }, - "value": "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "The ID of the cluster system assigned managed identity." - }, - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15', 'full').identity.principalId]" - }, - "clusterName": { - "type": "string", - "metadata": { - "description": "The name of the cluster." - }, - "value": "[parameters('clusterName')]" - }, - "clusterUri": { - "type": "string", - "metadata": { - "description": "The URI of the cluster." - }, - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15').uri]" - }, - "ingestionDbName": { - "type": "string", - "metadata": { - "description": "The name of the database for data ingestion." - }, - "value": "Ingestion" - }, - "hubDbName": { - "type": "string", - "metadata": { - "description": "The name of the database for queries." - }, - "value": "Hub" - }, - "clusterIngestionCapacity": { - "type": "int", - "metadata": { - "description": "Max ingestion capacity of the cluster." - }, - "value": "[coalesce(tryGet(variables('ingestionCapacity'), parameters('clusterSku')), 1)]" - } - } - } - }, - "dependsOn": [ - "core", - "infrastructure" - ] - }, - "dataFactoryResources": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "dataFactoryResources", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft FinOps hubs', 'Microsoft.FinOpsHubs', 'DataFactory', 'FinOps hub engine', variables('$fxv#1'))]" - }, - "hubName": { - "value": "[parameters('hubName')]" - }, - "dataFactoryName": { - "value": "[reference('core').outputs.dataFactoryName.value]" - }, - "location": { - "value": "[parameters('location')]" - }, - "tags": { - "value": "[reference('core').outputs.publisherTags.value]" - }, - "tagsByResource": { - "value": "[parameters('tagsByResource')]" - }, - "storageAccountName": { - "value": "[reference('core').outputs.storageAccountName.value]" - }, - "exportContainerName": { - "value": "[reference('cmExports').outputs.exportContainer.value]" - }, - "configContainerName": { - "value": "[reference('core').outputs.configContainer.value]" - }, - "ingestionContainerName": { - "value": "[reference('core').outputs.ingestionContainer.value]" - }, - "dataExplorerName": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterName.value))]", - "dataExplorerPrincipalId": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.principalId.value))]", - "dataExplorerIngestionDatabase": "[if(variables('useFabric'), createObject('value', 'Ingestion'), if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.ingestionDbName.value)))]", - "dataExplorerIngestionCapacity": "[if(variables('useFabric'), createObject('value', parameters('fabricCapacityUnits')), if(not(variables('deployDataExplorer')), createObject('value', 1), createObject('value', reference('dataExplorer').outputs.clusterIngestionCapacity.value)))]", - "dataExplorerUri": "[if(variables('useFabric'), createObject('value', parameters('fabricQueryUri')), if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterUri.value)))]", - "dataExplorerId": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterId.value))]", - "enableManagedExports": { - "value": "[parameters('enableManagedExports')]" - }, - "enablePublicAccess": { - "value": "[parameters('enablePublicAccess')]" - }, - "keyVaultName": "[if(empty(parameters('remoteHubStorageKey')), createObject('value', ''), createObject('value', reference('remoteHub').outputs.keyVaultName.value))]", - "remoteHubStorageUri": { - "value": "[parameters('remoteHubStorageUri')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "11163540491967572356" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0}', parameters('app').id)]", + "version": "[parameters('version')]" + } + }, + "resources": [] + } }, - "keyVaultSku": { - "type": "string" + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", + "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + }, + "resources": { + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", + "properties": { + "name": "[parameters('app').storage]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "storageAccount" + ] }, - "networkAddressPrefix": { - "type": "string" + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", + "properties": { + "name": "[parameters('app').keyVault]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "keyVault" + ] }, - "privateRouting": { - "type": "bool" + "dataFactory::managedVirtualNetwork": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "properties": {}, + "dependsOn": [ + "dataFactory" + ] }, - "publisherIsolation": { - "type": "bool" + "dataFactory::managedIntegrationRuntime": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "default", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('app').hub.location]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedVirtualNetwork" + ] }, - "storageInfrastructureEncryption": { - "type": "bool" + "dataFactory::linkedService_keyVault": { + "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "keyVault" + ] }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "dataFactory::linkedService_storageAccount": { + "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "storageAccount" + ] }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "dfsEndpoint" + ] + }, + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('app').hub.options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } + } + }, + "storageRoleAssignments": { + "copy": { + "name": "storageRoleAssignments", + "count": "[length(variables('factoryStorageRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "storageAccount" + ] }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "triggerManagerIdentity": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "dependsOn": [ + "dataFactory" + ] }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "triggerManagerIdentity" + ] }, - "keyVault": { - "type": "string" + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]", + "location": "[parameters('app').hub.location]", + "sku": { + "name": "[parameters('app').hub.options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" }, - "scripts": { - "type": "string" + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "blob" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] }, - "displayName": { - "type": "string" + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" }, - "suffix": { - "type": "string" + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getExportBody": { - "parameters": [ - { - "type": "string", - "name": "exportContainerName" + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('app').hub.options.keyVaultSku]", + "family": "A" + }, + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + } + }, + "dependsOn": [ + "dataFactory" + ] }, - { - "type": "string", - "name": "datasetType" + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} }, - { - "type": "string", - "name": "schemaVersion" + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('app').keyVault)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.keyVault]" + }, + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } + ] + }, + "dependsOn": [ + "keyVault" + ] }, - { - "type": "bool", - "name": "isMonthly" + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", + "getStoragePrivateEndpointConnections", + "keyVault" + ] }, - { - "type": "string", - "name": "exportFormat" + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections", + "keyVault" + ] }, - { - "type": "string", - "name": "compressionMode" + "getStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", + "stopTriggers", + "storageAccount" + ] }, - { - "type": "string", - "name": "partitionData" + "approveStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getStoragePrivateEndpointConnections", + "storageAccount" + ] }, - { - "type": "string", - "name": "dataOverwriteBehavior" + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('app').dataFactory]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } + } + }, + "dependsOn": [ + "appTelemetry", + "dataFactory", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" + ] } - ], - "output": { - "type": "string", - "value": "[format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}\", \"name\": \"@{{variables(''exportName'')}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'))]" - } - }, - "getExportBodyV2": { - "parameters": [ - { - "type": "string", - "name": "exportContainerName" - }, - { - "type": "string", - "name": "datasetType" - }, - { - "type": "string", - "name": "schemaVersion" - }, - { - "type": "bool", - "name": "isMonthly" - }, - { - "type": "string", - "name": "exportFormat" - }, - { - "type": "string", - "name": "compressionMode" - }, - { + }, + "outputs": { + "dataFactoryId": { "type": "string", - "name": "partitionData" + "metadata": { + "description": "Resource ID of the Data Factory instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" }, - { + "keyVaultId": { "type": "string", - "name": "dataOverwriteBehavior" + "metadata": { + "description": "Resource ID of the Key Vault instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" }, - { + "storageAccountId": { "type": "string", - "name": "recommendationScope" + "metadata": { + "description": "Resource ID of the storage account instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, - { + "principalId": { "type": "string", - "name": "recommendationLookbackPeriod" + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" }, - { + "triggerManagerIdentityName": { "type": "string", - "name": "resourceType" + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" } - ], - "output": { - "type": "string", - "value": "[if(equals(toLower(parameters('datasetType')), 'focuscost'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{10}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), if(equals(toLower(parameters('datasetType')), 'reservationdetails'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(or(equals(toLower(parameters('datasetType')), 'pricesheet'), equals(toLower(parameters('datasetType')), 'reservationtransactions')), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}}}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheCurrentMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(equals(toLower(parameters('datasetType')), 'reservationrecommendations'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [ {{ \"name\": \"reservationScope\", \"value\": \"{1}\" }}, {{ \"name\": \"resourceType\", \"value\": \"{2}\" }}, {{ \"name\": \"lookBackPeriod\", \"value\": \"{3}\" }}] }}}}, \"timeframe\": \"{4}\", \"type\": \"{5}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{6}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{7}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{8}\", \"partitionData\": \"{9}\", \"dataOverwriteBehavior\": \"{10}\", \"compressionMode\": \"{11}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{12}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{13}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), parameters('recommendationScope'), parameters('resourceType'), parameters('recommendationLookbackPeriod'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), 'undefined'))))]" - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. Temporary app placeholder for the deployments module." - } - }, - "hubName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub instance." - } - }, - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory instance." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. The name of the Azure Key Vault instance." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. The name of the Azure storage account instance." - } - }, - "exportContainerName": { - "type": "string", - "metadata": { - "description": "Required. The name of the container where Cost Management data is exported." - } - }, - "ingestionContainerName": { - "type": "string", - "metadata": { - "description": "Required. The name of the container where normalized data is ingested." - } - }, - "configContainerName": { - "type": "string", - "metadata": { - "description": "Required. The name of the container where normalized data is ingested." - } - }, - "dataExplorerName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics, if applicable." - } - }, - "dataExplorerId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Resource ID of the Azure Data Explorer cluster to use for advanced analytics, if applicable." - } - }, - "dataExplorerPrincipalId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. ID of the Azure Data Explorer cluster system assigned managed identity, if applicable." - } - }, - "dataExplorerUri": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. URI of the Azure Data Explorer cluster or Microsoft Fabric eventhouse query endpoint to use for advanced analytics, if applicable." - } - }, - "dataExplorerIngestionDatabase": { - "type": "string", - "defaultValue": "Ingestion", - "metadata": { - "description": "Optional. Name of the Azure Data Explorer ingestion database. Default: \"ingestion\"." - } - }, - "dataExplorerIngestionCapacity": { - "type": "int", - "defaultValue": 1, - "metadata": { - "description": "Optional. Azure Data Explorer ingestion capacity or Microsoft Fabric capacity units. Increase for non-dev/trial SKUs. Default: 1" - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." - } - }, - "remoteHubStorageUri": { - "type": "string", - "metadata": { - "description": "Optional. Remote storage account for ingestion dataset." - } - }, - "tags": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to apply to all resources." - } - }, - "tagsByResource": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." - } - }, - "enableManagedExports": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enable managed exports where your FinOps hub instance will create and run Cost Management exports on your behalf. Not supported for Microsoft Customer Agreement (MCA) billing profiles. Requires the ability to grant User Access Administrator role to FinOps hubs, which is required to create Cost Management exports. Default: true." - } - }, - "enablePublicAccess": { - "type": "bool", - "metadata": { - "description": "Required. Enable public access." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\n#\r\n$adfParams = @{\r\n ResourceGroupName = $env:DataFactoryResourceGroup\r\n DataFactoryName = $env:DataFactoryName\r\n}\r\n\r\n# Delete old triggers\r\n$triggers = Get-AzDataFactoryV2Trigger @adfParams -ErrorAction SilentlyContinue `\r\n| Where-Object { $_.Name -match '^msexports(_(setup|daily|monthly|extract|FileAdded))?$' }\r\n$DeploymentScriptOutputs[\"stopTriggers\"] = $triggers | Stop-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\r\n$DeploymentScriptOutputs[\"deleteTriggers\"] = $triggers | Remove-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\r\n\r\n# Delete old pipelines\r\n$DeploymentScriptOutputs[\"pipelines\"] = Get-AzDataFactoryV2Pipeline @adfParams -ErrorAction SilentlyContinue `\r\n| Where-Object { $_.Name -match '^(msexports_(backfill|extract|fill|get|run|setup|transform)|config_(BackfillData|ExportData|RunBackfill|RunExports))$' } `\r\n| Remove-AzDataFactoryV2Pipeline -Force -ErrorAction SilentlyContinue\r\n", - "$fxv#1": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nParam(\r\n [switch] $Stop\r\n)\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\nif (-not $Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\n# Loop thru triggers\r\n$env:Triggers.Split('|') `\r\n| ForEach-Object {\r\n $trigger = $_\r\n if ($Stop)\r\n {\r\n Write-Output \"Stopping trigger $trigger...\"\r\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force `\r\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\r\n }\r\n else\r\n {\r\n Write-Output \"Starting trigger $trigger...\"\r\n $triggerOutput = Start-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force\r\n }\r\n if ($triggerOutput)\r\n {\r\n Write-Output \"done...\"\r\n }\r\n else\r\n {\r\n Write-Output \"failed...\"\r\n }\r\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\r\n}\r\n\r\nif ($Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\r\n{\r\n $env:Pipelines.Split('|') `\r\n | ForEach-Object {\r\n Write-Output \"Running the init pipeline...\"\r\n Invoke-AzDataFactoryV2Pipeline `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -PipelineName $_\r\n }\r\n}\r\n", - "$fxv#2": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nParam(\r\n [switch] $Stop\r\n)\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\nif (-not $Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\n# Loop thru triggers\r\n$env:Triggers.Split('|') `\r\n| ForEach-Object {\r\n $trigger = $_\r\n if ($Stop)\r\n {\r\n Write-Output \"Stopping trigger $trigger...\"\r\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force `\r\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\r\n }\r\n else\r\n {\r\n Write-Output \"Starting trigger $trigger...\"\r\n $triggerOutput = Start-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force\r\n }\r\n if ($triggerOutput)\r\n {\r\n Write-Output \"done...\"\r\n }\r\n else\r\n {\r\n Write-Output \"failed...\"\r\n }\r\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\r\n}\r\n\r\nif ($Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\r\n{\r\n $env:Pipelines.Split('|') `\r\n | ForEach-Object {\r\n Write-Output \"Running the init pipeline...\"\r\n Invoke-AzDataFactoryV2Pipeline `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -PipelineName $_\r\n }\r\n}\r\n", - "focusSchemaVersion": "1.0", - "exportSchemaVersion": "2023-05-01", - "reservationDetailsSchemaVersion": "2023-03-01", - "ftkVersion": "12.0", - "ftkReleaseUri": "[if(endsWith(variables('ftkVersion'), '-dev'), 'https://github.com/microsoft/finops-toolkit/releases/latest/download', format('https://github.com/microsoft/finops-toolkit/releases/download/v{0}', variables('ftkVersion')))]", - "exportApiVersion": "2023-07-01-preview", - "hubDataExplorerName": "hubDataExplorer", - "deployDataExplorer": "[not(empty(parameters('dataExplorerId')))]", - "useFabric": "[and(not(variables('deployDataExplorer')), not(empty(parameters('dataExplorerUri'))))]", - "datasetPropsDefault": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().fileName}", - "type": "Expression" - }, - "folderPath": { - "value": "@{dataset().folderPath}", - "type": "Expression" - } - } - }, - "safeExportContainerName": "[replace(format('{0}', parameters('exportContainerName')), '-', '_')]", - "safeIngestionContainerName": "[replace(format('{0}', parameters('ingestionContainerName')), '-', '_')]", - "safeConfigContainerName": "[replace(format('{0}', parameters('configContainerName')), '-', '_')]", - "managedVnetName": "default", - "ingestionIdFileNameSeparator": "__", - "exportManifestAddedTriggerName": "[format('{0}_ManifestAdded', variables('safeExportContainerName'))]", - "ingestionManifestAddedTriggerName": "[format('{0}_ManifestAdded', variables('safeIngestionContainerName'))]", - "updateConfigTriggerName": "[format('{0}_SettingsUpdated', variables('safeConfigContainerName'))]", - "dailyTriggerName": "[format('{0}_DailySchedule', variables('safeConfigContainerName'))]", - "monthlyTriggerName": "[format('{0}_MonthlySchedule', variables('safeConfigContainerName'))]", - "allHubTriggers": [ - "[variables('exportManifestAddedTriggerName')]", - "[variables('ingestionManifestAddedTriggerName')]", - "[variables('updateConfigTriggerName')]", - "[variables('dailyTriggerName')]", - "[variables('monthlyTriggerName')]" - ], - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "storageRbacRoles": "[union(createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'), if(not(parameters('enableManagedExports')), createArray(), createArray('18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')))]", - "adxRbacRoles": [ - "b24988ac-6180-42a0-ab88-20f7382dd24c" - ] - }, - "resources": { - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('dataFactoryName')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('storageAccountName')]" - }, - "keyVault": { - "condition": "[not(empty(parameters('remoteHubStorageUri')))]", - "existing": true, - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('keyVaultName')]" - }, - "dataExplorerCluster": { - "condition": "[variables('deployDataExplorer')]", - "existing": true, - "type": "Microsoft.Kusto/clusters", - "apiVersion": "2023-08-15", - "name": "[parameters('dataExplorerName')]" - }, - "managedVirtualNetwork": { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('managedVnetName'))]", - "properties": {} + } + } + } }, - "managedIntegrationRuntime": { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ManagedIntegrationRuntime')]", + "schemaFiles": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.Exports_Storage.SchemaFiles", "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "[variables('managedVnetName')]", - "type": "ManagedVirtualNetworkReference" + "expressionEvaluationOptions": { + "scope": "inner" }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('location')]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "container": { + "value": "config" + }, + "files": { + "value": { + "schemas/actualcost_c360-2025-04.json": "[variables('$fxv#0')]", + "schemas/amortizedcost_c360-2025-04.json": "[variables('$fxv#1')]", + "schemas/focuscost_1.2.json": "[variables('$fxv#2')]", + "schemas/focuscost_1.2-preview.json": "[variables('$fxv#3')]", + "schemas/focuscost_1.0r2.json": "[variables('$fxv#4')]", + "schemas/focuscost_1.0.json": "[variables('$fxv#5')]", + "schemas/focuscost_1.0-preview(v1).json": "[variables('$fxv#6')]", + "schemas/pricesheet_2023-05-01_ea.json": "[variables('$fxv#7')]", + "schemas/pricesheet_2023-05-01_mca.json": "[variables('$fxv#8')]", + "schemas/reservationdetails_2023-03-01.json": "[variables('$fxv#9')]", + "schemas/reservationrecommendations_2023-05-01_ea.json": "[variables('$fxv#10')]", + "schemas/reservationrecommendations_2023-05-01_mca.json": "[variables('$fxv#11')]", + "schemas/reservationtransactions_2023-05-01_ea.json": "[variables('$fxv#12')]", + "schemas/reservationtransactions_2023-05-01_mca.json": "[variables('$fxv#13')]" + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "7314877606184110283" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app that storage is getting updated for." + } + }, + "container": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage container to create or update." + } + }, + "files": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." + } + }, + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." + } } - } - } - }, - "dependsOn": [ - "managedVirtualNetwork" - ] - }, - "storageManagedPrivateEndpoint": { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), parameters('storageAccountName'))]", - "properties": { - "name": "[parameters('storageAccountName')]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] - }, - "dependsOn": [ - "managedVirtualNetwork", - "storageAccount" - ] - }, - "keyVaultManagedPrivateEndpoint": { - "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), parameters('keyVaultName'))]", - "properties": { - "name": "[parameters('keyVaultName')]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] - }, - "dependsOn": [ - "keyVault", - "managedVirtualNetwork" - ] - }, - "dataExplorerManagedPrivateEndpoint": { - "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), variables('hubDataExplorerName'))]", - "properties": { - "name": "[variables('hubDataExplorerName')]", - "groupId": "cluster", - "privateLinkResourceId": "[parameters('dataExplorerId')]", - "fqdns": [ - "[parameters('dataExplorerUri')]" - ] - }, - "dependsOn": [ - "managedVirtualNetwork" - ] - }, - "triggerManagerIdentity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('dataFactoryName'))]", - "location": "[parameters('location')]", - "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]" - }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('dataFactoryName'))]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('dataFactoryName'))))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "triggerManagerIdentity" - ] - }, - "factoryIdentityStorageRoleAssignments": { - "copy": { - "name": "factoryIdentityStorageRoleAssignments", - "count": "[length(variables('storageRbacRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), variables('storageRbacRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('storageRbacRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory" - ] - }, - "factoryIdentityDataExplorerRoleAssignments": { - "copy": { - "name": "factoryIdentityDataExplorerRoleAssignments", - "count": "[length(variables('adxRbacRoles'))]" - }, - "condition": "[variables('deployDataExplorer')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Kusto/clusters/{0}', parameters('dataExplorerName'))]", - "name": "[guid(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), variables('adxRbacRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('adxRbacRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory" - ] - }, - "linkedService_keyVault": { - "condition": "[not(empty(parameters('remoteHubStorageUri')))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('keyVaultName'))]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('keyVaultName')), '2023-02-01').vaultUri]" - }, - "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" - }, - "dependsOn": [ - "managedIntegrationRuntime" - ] - }, - "linkedService_storageAccount": { - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('storageAccountName'))]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName')), '2021-08-01').primaryEndpoints.dfs]" - }, - "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" - }, - "dependsOn": [ - "managedIntegrationRuntime" - ] - }, - "linkedService_dataExplorer": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('hubDataExplorerName'))]", - "properties": { - "type": "AzureDataExplorer", - "parameters": { - "database": { - "type": "String", - "defaultValue": "[parameters('dataExplorerIngestionDatabase')]" - } - }, - "typeProperties": { - "endpoint": "[parameters('dataExplorerUri')]", - "database": "@{linkedService().database}", - "tenant": "[reference('dataFactory', '2018-06-01', 'full').identity.tenantId]", - "servicePrincipalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - }, - "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" - }, - "dependsOn": [ - "dataFactory", - "managedIntegrationRuntime" - ] - }, - "linkedService_remoteHubStorage": { - "condition": "[not(empty(parameters('remoteHubStorageUri')))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'remoteHubStorage')]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[parameters('remoteHubStorageUri')]", - "accountKey": { - "type": "AzureKeyVaultSecret", - "store": { - "referenceName": "[parameters('keyVaultName')]", - "type": "LinkedServiceReference" - }, - "secretName": "[format('{0}-storage-key', toLower(parameters('hubName')))]" - } - }, - "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" - }, - "dependsOn": [ - "linkedService_keyVault", - "managedIntegrationRuntime" - ] - }, - "linkedService_ftkRepo": { - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ftkRepo')]", - "properties": { - "parameters": { - "filePath": { - "type": "string" - } - }, - "annotations": [], - "type": "HttpServer", - "typeProperties": { - "url": "@concat('https://github.com/microsoft/finops-toolkit/', linkedService().filePath)", - "enableServerCertificateValidation": true, - "authenticationType": "Anonymous" - }, - "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" - }, - "dependsOn": [ - "managedIntegrationRuntime" - ] - }, - "dataset_config": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeConfigContainerName'))]", - "properties": { - "annotations": [], - "parameters": { - "fileName": { - "type": "String", - "defaultValue": "settings.json" }, - "folderPath": { - "type": "String", - "defaultValue": "[parameters('configContainerName')]" - } - }, - "type": "Json", - "typeProperties": "[variables('datasetPropsDefault')]", - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('storageAccountName')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_storageAccount" - ] - }, - "dataset_manifest": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'manifest')]", - "properties": { - "annotations": [], - "parameters": { - "fileName": { - "type": "String", - "defaultValue": "manifest.json" + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" }, - "folderPath": { - "type": "String", - "defaultValue": "[parameters('exportContainerName')]" - } - }, - "type": "Json", - "typeProperties": "[variables('datasetPropsDefault')]", - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('storageAccountName')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_storageAccount" - ] - }, - "dataset_msexports": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeExportContainerName'))]", - "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" + "resources": { + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "properties": { + "publicAccess": "None", + "metadata": {} + } }, - "fileSystem": "[variables('safeExportContainerName')]" - }, - "columnDelimiter": ",", - "escapeChar": "\"", - "quoteChar": "\"", - "firstRowAsHeader": true - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('storageAccountName')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_storageAccount" - ] - }, - "dataset_msexports_gzip": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_gzip', variables('safeExportContainerName')))]", - "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" + "storageAccount::blobService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]" + }, + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Identity', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_blobManager', parameters('app').storage)]" + }, + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "roles": { + "value": [ + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2980528181281411934" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the identity is associated with." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the user assigned identity." + } + }, + "roleAssignmentResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource access is being granted for." + } + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of RBAC role assignment GUIDs." + } + } + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "location": "[parameters('app').hub.location]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." + }, + "value": "[parameters('identityName')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" + } + } + } + } }, - "fileSystem": "[variables('safeExportContainerName')]" - }, - "columnDelimiter": ",", - "escapeChar": "\"", - "quoteChar": "\"", - "firstRowAsHeader": true, - "compressionCodec": "Gzip" - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('storageAccountName')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_storageAccount" - ] - }, - "dataset_msexports_parquet": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_parquet', variables('safeExportContainerName')))]", - "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" + "uploadFiles": { + "condition": "[variables('hasFiles')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Upload', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[reference('identity').outputs.name.value]" + }, + "environmentVariables": { + "value": [ + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" + }, + { + "name": "files", + "value": "[string(parameters('files'))]" + } + ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } + } + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" }, - "fileSystem": "[variables('safeExportContainerName')]" - } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('storageAccountName')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_storageAccount" - ] - }, - "dataset_ingestion": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeIngestionContainerName'))]", - "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" + "filesUploaded": { + "type": "int", + "metadata": { + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" }, - "fileSystem": "[variables('safeIngestionContainerName')]" - } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[if(empty(parameters('remoteHubStorageUri')), parameters('storageAccountName'), 'remoteHubStorage')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_remoteHubStorage", - "linkedService_storageAccount" - ] - }, - "dataset_ingestion_files": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_files', variables('safeIngestionContainerName')))]", - "properties": { - "annotations": [], - "parameters": { - "folderPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileSystem": "[variables('safeIngestionContainerName')]", - "folderPath": { - "value": "@dataset().folderPath", - "type": "Expression" + "identityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" } } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[if(empty(parameters('remoteHubStorageUri')), parameters('storageAccountName'), 'remoteHubStorage')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_remoteHubStorage", - "linkedService_storageAccount" - ] - }, - "dataset_dataExplorer": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('hubDataExplorerName'))]", - "properties": { - "type": "AzureDataExplorerTable", - "linkedServiceName": { - "parameters": { - "database": "@dataset().database" - }, - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference" - }, - "parameters": { - "database": { - "type": "String", - "defaultValue": "[parameters('dataExplorerIngestionDatabase')]" - }, - "table": { - "type": "String" - } - }, - "typeProperties": { - "table": { - "value": "@dataset().table", - "type": "Expression" - } } }, "dependsOn": [ - "linkedService_dataExplorer" + "appRegistration" ] }, - "dataset_ftkReleaseFile": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ftkReleaseFile')]", + "exportContainer": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.Exports_Storage.ExportContainer", "properties": { - "linkedServiceName": { - "referenceName": "ftkRepo", - "type": "LinkedServiceReference" + "expressionEvaluationOptions": { + "scope": "inner" }, + "mode": "Incremental", "parameters": { - "fileName": { - "type": "string" + "app": { + "value": "[parameters('app')]" }, - "version": { - "type": "string", - "defaultValue": "[variables('ftkVersion')]" + "container": { + "value": "[variables('MSEXPORTS')]" } }, - "annotations": [], - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "HttpServerLocation", - "relativeUrl": { - "value": "@concat('releases/download/v', dataset().version, '/', dataset().fileName)", - "type": "Expression" - } - }, - "columnDelimiter": ",", - "escapeChar": "\\", - "firstRowAsHeader": true, - "quoteChar": "\"" - }, - "schema": [] - }, - "dependsOn": [ - "linkedService_ftkRepo" - ] - }, - "trigger_DailySchedule": { - "condition": "[parameters('enableManagedExports')]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('dailyTriggerName'))]", - "properties": { - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[format('{0}_StartExportProcess', variables('safeConfigContainerName'))]", - "type": "PipelineReference" - }, - "parameters": { - "Recurrence": "Daily" - } - } - ], - "type": "ScheduleTrigger", - "typeProperties": { - "recurrence": { - "frequency": "Hour", - "interval": 24, - "startTime": "2023-01-01T01:01:00", - "timeZone": "[reference('azuretimezones').outputs.Timezone.value]" - } - } - }, - "dependsOn": [ - "azuretimezones", - "pipeline_StartExportProcess", - "stopTriggers" - ] - }, - "trigger_MonthlySchedule": { - "condition": "[parameters('enableManagedExports')]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('monthlyTriggerName'))]", - "properties": { - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[format('{0}_StartExportProcess', variables('safeConfigContainerName'))]", - "type": "PipelineReference" - }, - "parameters": { - "Recurrence": "Monthly" - } - } - ], - "type": "ScheduleTrigger", - "typeProperties": { - "recurrence": { - "frequency": "Month", - "interval": 1, - "startTime": "2023-01-05T01:11:00", - "timeZone": "[reference('azuretimezones').outputs.Timezone.value]", - "schedule": { - "monthDays": [ - 2, - 5, - 19 - ] - } - } - } - }, - "dependsOn": [ - "azuretimezones", - "pipeline_StartExportProcess", - "stopTriggers" - ] - }, - "pipeline_InitializeHub": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_InitializeHub', variables('safeConfigContainerName')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "7314877606184110283" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "formatSettings": { - "type": "JsonReadSettings" + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } } }, - "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", - "type": "DatasetReference" - } - } - }, - { - "name": "Set Version", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - ], - "userProperties": [], - "typeProperties": { - "variableName": "version", - "value": { - "value": "@activity('Get Config').output.firstRow.version", - "type": "Expression" + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - } - }, - { - "name": "Set Scopes", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - ], - "userProperties": [], - "typeProperties": { - "variableName": "scopes", - "value": { - "value": "@string(activity('Get Config').output.firstRow.scopes)", - "type": "Expression" + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } } }, - { - "name": "Set Retention", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app that storage is getting updated for." } - ], - "userProperties": [], - "typeProperties": { - "variableName": "retention", - "value": { - "value": "@string(activity('Get Config').output.firstRow.retention)", - "type": "Expression" + }, + "container": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage container to create or update." + } + }, + "files": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." + } + }, + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." } } }, - { - "name": "Until Capacity Is Available", - "type": "Until", - "dependsOn": [ - { - "activity": "Set Version", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Retention", - "dependencyConditions": [ - "Succeeded" - ] + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" + }, + "resources": { + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "properties": { + "publicAccess": "None", + "metadata": {} } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@equals(variables('tryAgain'), false)", - "type": "Expression" - }, - "activities": [ - { - "name": "Confirm Ingestion Capacity", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + }, + "storageAccount::blobService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]" + }, + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Identity', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" }, - "userProperties": [], - "typeProperties": { - "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", - "commandTimeout": "00:20:00" + "identityName": { + "value": "[format('{0}_blobManager', parameters('app').storage)]" }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "roles": { + "value": [ + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2980528181281411934" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } - } - }, - { - "name": "If Has Capacity", - "type": "IfCondition", - "dependsOn": [ + }, + "functions": [ { - "activity": "Confirm Ingestion Capacity", - "dependencyConditions": [ - "Succeeded" - ] + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } } ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", - "type": "Expression" + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the identity is associated with." + } }, - "ifFalseActivities": [ + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the user assigned identity." + } + }, + "roleAssignmentResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource access is being granted for." + } + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of RBAC role assignment GUIDs." + } + } + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "location": "[parameters('app').hub.location]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." + }, + "value": "[parameters('identityName')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" + } + } + } + } + }, + "uploadFiles": { + "condition": "[variables('hasFiles')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Upload', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[reference('identity').outputs.name.value]" + }, + "environmentVariables": { + "value": [ { - "name": "Wait for Ingestion", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 15 - } + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" }, { - "name": "Try Again", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait for Ingestion", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false + "name": "files", + "value": "[string(parameters('files'))]" + } + ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": true + "value": { + "type": "string" } } - ], - "ifTrueActivities": [ - { - "name": "Set ingestion policy in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "userProperties": [], - "typeProperties": { - "command": { - "value": "[if(variables('useFabric'), format('.show database {0} policy managed_identity', parameters('dataExplorerIngestionDatabase')), format('.alter-merge database {0} policy managed_identity \"[ {{ ''ObjectId'' : ''{1}'', ''AllowedUsages'' : ''NativeIngestion'' }}]\"', parameters('dataExplorerIngestionDatabase'), parameters('dataExplorerPrincipalId')))]", - "type": "Expression" - }, - "commandTimeout": "00:20:00" + "name": { + "type": "string" }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" - } - } - }, - { - "name": "Save Hub Settings in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Set ingestion policy in ADX", - "dependencyConditions": [ - "Succeeded" - ] + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "command": { - "value": "@concat('.append HubSettingsLog <| print version=\"', variables('version'), '\",scopes=dynamic(', variables('scopes'), '),retention=dynamic(', variables('retention'), ') | extend scopes = iff(isnull(scopes[0]), pack_array(scopes), scopes) | mv-apply scopeObj = scopes on (where isnotempty(scopeObj.scope) | summarize scopes = make_set(scopeObj.scope))')", - "type": "Expression" - }, - "commandTimeout": "00:20:00" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } } } }, - { - "name": "Update PricingUnits in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Save Hub Settings in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace PricingUnits <| externaldata(x_PricingUnitDescription: string, AccountTypes: string, x_PricingBlockSize: decimal, PricingUnit: string)[@\"{0}/PricingUnits.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away AccountTypes', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" - } + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } - }, - { - "name": "Update Regions in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update PricingUnits in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace Regions <| externaldata(ResourceLocation: string, RegionId: string, RegionName: string)[@\"{0}/Regions.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" + "networkName": { + "type": "string" }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } } } }, - { - "name": "Update ResourceTypes in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update Regions in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace ResourceTypes <| externaldata(x_ResourceType: string, SingularDisplayName: string, PluralDisplayName: string, LowerSingularDisplayName: string, LowerPluralDisplayName: string, IsPreview: bool, Description: string, IconUri: string, Links: string)[@\"{0}/ResourceTypes.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away Links', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" - } + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" } }, - { - "name": "Update Services in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update ResourceTypes in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace Services <| externaldata(x_ConsumedService: string, x_ResourceType: string, ServiceName: string, ServiceCategory: string, ServiceSubcategory: string, PublisherName: string, x_PublisherCategory: string, x_Environment: string, x_ServiceModel: string)[@\"{0}/Services.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" + "name": { + "type": "string" }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" - } + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, - { - "name": "Ingestion Complete", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Update Services in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } - ] - } - }, - { - "name": "Abort On Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "If Has Capacity", - "dependencyConditions": [ - "Failed" + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" ] } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false } } - ], - "timeout": "0.02:00:00" + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" + }, + "filesUploaded": { + "type": "int", + "metadata": { + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" + }, + "identityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + } + } + } + }, + "dependsOn": [ + "appRegistration" + ] + }, + "trigger_ExportManifestAdded": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.Exports_ADF.ExportManifestTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('app').dataFactory]" + }, + "triggerName": { + "value": "[format('{0}_ManifestAdded', variables('MSEXPORTS'))]" + }, + "pipelineName": { + "value": "[format('{0}_ExecuteETL', variables('MSEXPORTS'))]" + }, + "pipelineParameters": { + "value": { + "folderPath": "@triggerBody().folderPath", + "fileName": "@triggerBody().fileName" + } + }, + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "storageContainer": { + "value": "[variables('MSEXPORTS')]" + }, + "storagePathEndsWith": { + "value": "manifest.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14264521107451792604" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." + } + }, + "triggerName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory trigger to create or update." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storageContainer": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storagePathStartsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." + } + }, + "storagePathEndsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } + }, + "pipelineName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } + }, + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + } + } + }, + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" + }, + "parameters": "[parameters('pipelineParameters')]" + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] + } + } + } + ] + } + }, + "dependsOn": [ + "appRegistration", + "dataFactory::pipeline_ExecuteExportsETL" + ] + } + }, + "outputs": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Properties of the hub app." + }, + "value": "[parameters('app')]" + }, + "exportContainer": { + "type": "string", + "metadata": { + "description": "Name of the container used for Cost Management exports." + }, + "value": "[reference('exportContainer').outputs.containerName.value]" + }, + "schemaFilesUploaded": { + "type": "int", + "metadata": { + "description": "Number of schema files uploaded." + }, + "value": "[reference('schemaFiles').outputs.filesUploaded.value]" + } + } + } + }, + "dependsOn": [ + "core" + ] + }, + "cmManagedExports": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.ManagedExports", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft.CostManagement', 'ManagedExports')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "15949887161767442453" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getExportBody": { + "parameters": [ + { + "type": "string", + "name": "exportContainerName" + }, + { + "type": "string", + "name": "datasetType" + }, + { + "type": "string", + "name": "schemaVersion" + }, + { + "type": "bool", + "name": "isMonthly" + }, + { + "type": "string", + "name": "exportFormat" + }, + { + "type": "string", + "name": "compressionMode" + }, + { + "type": "string", + "name": "partitionData" + }, + { + "type": "string", + "name": "dataOverwriteBehavior" + } + ], + "output": { + "type": "string", + "value": "[format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}\", \"name\": \"@{{variables(''exportName'')}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'))]" + } + }, + "getExportBodyV2": { + "parameters": [ + { + "type": "string", + "name": "exportContainerName" + }, + { + "type": "string", + "name": "datasetType" + }, + { + "type": "bool", + "name": "isMonthly" + }, + { + "type": "string", + "name": "exportFormat" + }, + { + "type": "string", + "name": "compressionMode" + }, + { + "type": "string", + "name": "partitionData" + }, + { + "type": "string", + "name": "dataOverwriteBehavior" + }, + { + "type": "string", + "name": "recommendationScope" + }, + { + "type": "string", + "name": "recommendationLookbackPeriod" + }, + { + "type": "string", + "name": "resourceType" } - }, + ], + "output": { + "type": "string", + "value": "[if(equals(toLower(parameters('datasetType')), 'focuscost'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{10}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), if(equals(toLower(parameters('datasetType')), 'reservationdetails'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(or(equals(toLower(parameters('datasetType')), 'pricesheet'), equals(toLower(parameters('datasetType')), 'reservationtransactions')), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}}}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheCurrentMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(equals(toLower(parameters('datasetType')), 'reservationrecommendations'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [ {{ \"name\": \"reservationScope\", \"value\": \"{1}\" }}, {{ \"name\": \"resourceType\", \"value\": \"{2}\" }}, {{ \"name\": \"lookBackPeriod\", \"value\": \"{3}\" }}] }}}}, \"timeframe\": \"{4}\", \"type\": \"{5}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{6}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{7}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{8}\", \"partitionData\": \"{9}\", \"dataOverwriteBehavior\": \"{10}\", \"compressionMode\": \"{11}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{12}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{13}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], parameters('recommendationScope'), parameters('resourceType'), parameters('recommendationLookbackPeriod'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), 'undefined'))))]" + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + } + }, + "variables": { + "CONFIG": "config", + "MSEXPORTS": "msexports", + "exportsApiVersion": "2023-07-01-preview", + "exportDataVersions": { + "focuscost": "1.2-preview", + "pricesheet": "2023-03-01", + "reservationdetails": "2023-03-01", + "reservationrecommendations": "2023-05-01", + "reservationtransactions": "2023-05-01" + }, + "finOpsToolkitVersion": "12.0" + }, + "resources": { + "dataFactory::dataset_config": { + "existing": true, + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]" + }, + "dataFactory::trigger_DailySchedule": { + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_DailySchedule', variables('CONFIG')))]", + "properties": { + "pipelines": [ { - "name": "Timeout Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Until Capacity Is Available", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": "Data Explorer ingestion timed out after 2 hours while waiting for available capacity. Please re-run this pipeline to re-attempt ingestion. If you continue to see this error, please report an issue at https://aka.ms/ftk/ideas.", - "errorCode": "DataExplorerIngestionTimeout" + "pipelineReference": { + "referenceName": "[format('{0}_StartExportProcess', variables('CONFIG'))]", + "type": "PipelineReference" + }, + "parameters": { + "Recurrence": "Daily" } } ], - "concurrency": 1, - "variables": { - "version": { - "type": "String" - }, - "scopes": { - "type": "String" - }, - "retention": { - "type": "String" - }, - "tryAgain": { - "type": "Boolean", - "defaultValue": true + "type": "ScheduleTrigger", + "typeProperties": { + "recurrence": { + "frequency": "Hour", + "interval": 24, + "startTime": "2023-01-01T01:01:00", + "timeZone": "[reference('timeZones').outputs.Timezone.value]" } } }, "dependsOn": [ - "dataset_config", - "linkedService_dataExplorer" - ], - "metadata": { - "description": "Initializes the hub instance based on the configuration settings." - } + "dataFactory::pipeline_StartExportProcess", + "timeZones" + ] + }, + "dataFactory::trigger_MonthlySchedule": { + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_MonthlySchedule', variables('CONFIG')))]", + "properties": { + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[format('{0}_StartExportProcess', variables('CONFIG'))]", + "type": "PipelineReference" + }, + "parameters": { + "Recurrence": "Monthly" + } + } + ], + "type": "ScheduleTrigger", + "typeProperties": { + "recurrence": { + "frequency": "Month", + "interval": 1, + "startTime": "2023-01-05T01:11:00", + "timeZone": "[reference('timeZones').outputs.Timezone.value]", + "schedule": { + "monthDays": [ + 2, + 5, + 19 + ] + } + } + } + }, + "dependsOn": [ + "dataFactory::pipeline_StartExportProcess", + "timeZones" + ] }, - "pipeline_StartBackfillProcess": { - "condition": "[parameters('enableManagedExports')]", + "dataFactory::pipeline_StartBackfillProcess": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_StartBackfillProcess', variables('safeConfigContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_StartBackfillProcess', variables('CONFIG')))]", "properties": { "activities": [ { @@ -12061,7 +12908,7 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", + "referenceName": "[variables('CONFIG')]", "type": "DatasetReference", "parameters": { "fileName": { @@ -12227,7 +13074,7 @@ "userProperties": [], "typeProperties": { "pipeline": { - "referenceName": "[format('{0}_RunBackfillJob', variables('safeConfigContainerName'))]", + "referenceName": "[format('{0}_RunBackfillJob', variables('CONFIG'))]", "type": "PipelineReference" }, "waitOnCompletion": true, @@ -12255,11 +13102,11 @@ }, "storageAccountId": { "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, "finOpsHub": { "type": "String", - "defaultValue": "[parameters('hubName')]" + "defaultValue": "[parameters('app').hub.name]" }, "resourceManagementUri": { "type": "String", @@ -12271,7 +13118,7 @@ }, "folderPath": { "type": "String", - "defaultValue": "[parameters('configContainerName')]" + "defaultValue": "[variables('CONFIG')]" }, "endDate": { "type": "String" @@ -12288,18 +13135,13 @@ } }, "dependsOn": [ - "dataset_config", - "pipeline_RunBackfillJob" - ], - "metadata": { - "description": "Runs the backfill job for each month based on retention settings." - } + "dataFactory::pipeline_RunBackfillJob" + ] }, - "pipeline_RunBackfillJob": { - "condition": "[parameters('enableManagedExports')]", + "dataFactory::pipeline_RunBackfillJob": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_RunBackfillJob', variables('safeConfigContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_RunBackfillJob', variables('CONFIG')))]", "properties": { "activities": [ { @@ -12327,7 +13169,7 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", + "referenceName": "[variables('CONFIG')]", "type": "DatasetReference", "parameters": { "fileName": { @@ -12400,7 +13242,8 @@ { "activity": "Set Scopes", "dependencyConditions": [ - "Succeeded" + "Succeeded", + "Failed" ] }, { @@ -12476,14 +13319,14 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}/run?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}/run?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "POST", "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunBackfill@{0}', variables('ftkVersion'))]", + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunBackfill@{0}', variables('finOpsToolkitVersion'))]", "Content-Type": "application/json", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "body": "{\"timePeriod\" : { \"from\" : \"@{pipeline().parameters.StartDate}\", \"to\" : \"@{pipeline().parameters.EndDate}\" }}", "authentication": { @@ -12514,11 +13357,11 @@ }, "storageAccountId": { "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, "finOpsHub": { "type": "String", - "defaultValue": "[parameters('hubName')]" + "defaultValue": "[parameters('app').hub.name]" }, "resourceManagementUri": { "type": "String", @@ -12530,25 +13373,18 @@ }, "folderPath": { "type": "String", - "defaultValue": "[parameters('configContainerName')]" + "defaultValue": "[variables('CONFIG')]" }, "scopesArray": { "type": "Array" } } - }, - "dependsOn": [ - "dataset_config" - ], - "metadata": { - "description": "Creates and triggers exports for all defined scopes for the specified date range." } }, - "pipeline_StartExportProcess": { - "condition": "[parameters('enableManagedExports')]", + "dataFactory::pipeline_StartExportProcess": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_StartExportProcess', variables('safeConfigContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_StartExportProcess', variables('CONFIG')))]", "properties": { "activities": [ { @@ -12576,7 +13412,7 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", + "referenceName": "[variables('CONFIG')]", "type": "DatasetReference", "parameters": { "fileName": { @@ -12649,7 +13485,8 @@ { "activity": "Set Scopes", "dependencyConditions": [ - "Succeeded" + "Succeeded", + "Failed" ] }, { @@ -12705,7 +13542,7 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "GET", @@ -12732,7 +13569,7 @@ "userProperties": [], "typeProperties": { "pipeline": { - "referenceName": "[format('{0}_RunExportJobs', variables('safeConfigContainerName'))]", + "referenceName": "[format('{0}_RunExportJobs', variables('CONFIG'))]", "type": "PipelineReference" }, "waitOnCompletion": true, @@ -12766,11 +13603,11 @@ }, "folderPath": { "type": "String", - "defaultValue": "[parameters('configContainerName')]" + "defaultValue": "[variables('CONFIG')]" }, "finOpsHub": { "type": "String", - "defaultValue": "[parameters('hubName')]" + "defaultValue": "[parameters('app').hub.name]" }, "resourceManagementUri": { "type": "String", @@ -12782,18 +13619,13 @@ } }, "dependsOn": [ - "dataset_config", - "pipeline_RunExportJobs" - ], - "metadata": { - "description": "Gets a list of all Cost Management exports configured for this hub based on the scopes defined in settings.json, then runs each export using the config_RunExportJobs pipeline." - } + "dataFactory::pipeline_RunExportJobs" + ] }, - "pipeline_RunExportJobs": { - "condition": "[parameters('enableManagedExports')]", + "dataFactory::pipeline_RunExportJobs": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_RunExportJobs', variables('safeConfigContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_RunExportJobs', variables('CONFIG')))]", "properties": { "activities": [ { @@ -12834,12 +13666,12 @@ "typeProperties": { "method": "POST", "url": { - "value": "[format('@{{replace(toLower(concat(variables(''resourceManagementUri''),item().id)), ''com//'', ''com/'')}}/run?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{replace(toLower(concat(variables(''resourceManagementUri''),item().id)), ''com//'', ''com/'')}}/run?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "body": " ", "authentication": { @@ -12875,22 +13707,18 @@ }, "hubName": { "type": "String", - "defaultValue": "[parameters('hubName')]" + "defaultValue": "[parameters('app').hub.name]" } } }, "dependsOn": [ - "dataset_config" - ], - "metadata": { - "description": "Runs the specified Cost Management exports." - } + "dataFactory::dataset_config" + ] }, - "pipeline_ConfigureExports": { - "condition": "[parameters('enableManagedExports')]", + "dataFactory::pipeline_ConfigureExports": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ConfigureExports', variables('safeConfigContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ConfigureExports', variables('CONFIG')))]", "properties": { "activities": [ { @@ -12918,7 +13746,7 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", + "referenceName": "[variables('CONFIG')]", "type": "DatasetReference", "parameters": { "fileName": { @@ -12988,7 +13816,8 @@ { "activity": "Save Scopes", "dependencyConditions": [ - "Succeeded" + "Succeeded", + "Failed" ] }, { @@ -13069,7 +13898,7 @@ "value": "ea", "activities": [ { - "name": "EA open month focus export", + "name": "Open month focus export", "type": "WebActivity", "dependsOn": [], "policy": { @@ -13082,17 +13911,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13104,11 +13933,11 @@ } }, { - "name": "EA closed month focus export", + "name": "Closed month focus export", "type": "WebActivity", "dependsOn": [ { - "activity": "EA open month focus export", + "activity": "Open month focus export", "dependencyConditions": [ "Succeeded" ] @@ -13124,17 +13953,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13146,11 +13975,11 @@ } }, { - "name": "EA monthly pricesheet export", + "name": "Monthly pricesheet export", "type": "WebActivity", "dependsOn": [ { - "activity": "EA closed month focus export", + "activity": "Closed month focus export", "dependencyConditions": [ "Succeeded" ] @@ -13166,17 +13995,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'Pricesheet', variables('exportSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'Pricesheet', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13192,7 +14021,7 @@ "type": "WebActivity", "dependsOn": [ { - "activity": "EA monthly pricesheet export", + "activity": "Monthly pricesheet export", "dependencyConditions": [ "Succeeded" ] @@ -13209,12 +14038,12 @@ "typeProperties": { "method": "POST", "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}/run?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}/run?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "body": " ", "authentication": { @@ -13227,11 +14056,11 @@ } }, { - "name": "EA daily reservation details export", + "name": "Daily reservation details export", "type": "WebActivity", "dependsOn": [ { - "activity": "EA monthly pricesheet export", + "activity": "Monthly pricesheet export", "dependencyConditions": [ "Succeeded" ] @@ -13247,17 +14076,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationDetails', variables('reservationDetailsSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationDetails', false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationDetails@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationDetails@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13269,11 +14098,11 @@ } }, { - "name": "EA daily reservation transactions export", + "name": "Daily reservation transactions export", "type": "WebActivity", "dependsOn": [ { - "activity": "EA daily reservation details export", + "activity": "Daily reservation details export", "dependencyConditions": [ "Succeeded" ] @@ -13289,17 +14118,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationtransactions''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationtransactions''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationTransactions', variables('exportSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationTransactions', false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationTransactions@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationTransactions@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13311,11 +14140,11 @@ } }, { - "name": "EA daily shared 30day virtualmachines", + "name": "Daily shared 30day virtual machines", "type": "WebActivity", "dependsOn": [ { - "activity": "EA daily reservation transactions export", + "activity": "Daily reservation transactions export", "dependencyConditions": [ "Succeeded" ] @@ -13331,17 +14160,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-recommendations-shared-last30days-virtualmachines''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-recommendations-shared-last30days-virtualmachines''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationRecommendations', variables('exportSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', 'Shared', 'Last30Days', 'VirtualMachines')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationRecommendations', false(), 'CSV', 'None', 'true', 'CreateNewReport', 'Shared', 'Last30Days', 'VirtualMachines')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationRecommendations.VM.Shared.30d@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationRecommendations.VM.Shared.30d@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13371,17 +14200,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13413,17 +14242,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13454,983 +14283,2779 @@ } ] } - ], - "defaultActivities": [ - { - "name": "Export Type Not Defined Error", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to determine the export scope type for :',variables('exportScope'))", - "type": "Expression" - }, - "errorCode": "ExportTypeNotDefined" + ], + "defaultActivities": [ + { + "name": "Export Type Not Defined Error", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to determine the export scope type for :',variables('exportScope'))", + "type": "Expression" + }, + "errorCode": "ExportTypeNotDefined" + } + } + ] + } + } + ] + } + } + ], + "concurrency": 1, + "variables": { + "scopesArray": { + "type": "Array" + }, + "exportName": { + "type": "String" + }, + "exportScope": { + "type": "String" + }, + "exportScopeType": { + "type": "String" + }, + "storageAccountId": { + "type": "String", + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "finOpsHub": { + "type": "String", + "defaultValue": "[parameters('app').hub.name]" + }, + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" + }, + "fileName": { + "type": "String", + "defaultValue": "settings.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[variables('CONFIG')]" + } + } + } + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]" + }, + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]" + }, + "appRegistration": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.ManagedExports_Register", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "version": { + "value": "[variables('finOpsToolkitVersion')]" + }, + "features": { + "value": [ + "DataFactory" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "5436870138046688593" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + }, + "version": { + "type": "string", + "metadata": { + "description": "Required. Version number of the FinOps hub app." + } + }, + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." + } + }, + "storageRoles": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + } + }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0}', parameters('app').id)]", + "version": "[parameters('version')]" + } + }, + "resources": [] + } + }, + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", + "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + }, + "resources": { + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", + "properties": { + "name": "[parameters('app').storage]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "storageAccount" + ] + }, + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", + "properties": { + "name": "[parameters('app').keyVault]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "keyVault" + ] + }, + "dataFactory::managedVirtualNetwork": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "properties": {}, + "dependsOn": [ + "dataFactory" + ] + }, + "dataFactory::managedIntegrationRuntime": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "default", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('app').hub.location]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedVirtualNetwork" + ] + }, + "dataFactory::linkedService_keyVault": { + "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "keyVault" + ] + }, + "dataFactory::linkedService_storageAccount": { + "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "storageAccount" + ] + }, + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "dfsEndpoint" + ] + }, + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('app').hub.options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } + } + }, + "storageRoleAssignments": { + "copy": { + "name": "storageRoleAssignments", + "count": "[length(variables('factoryStorageRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "storageAccount" + ] + }, + "triggerManagerIdentity": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "dependsOn": [ + "dataFactory" + ] + }, + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "triggerManagerIdentity" + ] + }, + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]", + "location": "[parameters('app').hub.location]", + "sku": { + "name": "[parameters('app').hub.options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" + }, + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + }, + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "blob" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('app').hub.options.keyVaultSku]", + "family": "A" + }, + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + } + }, + "dependsOn": [ + "dataFactory" + ] + }, + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('app').keyVault)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.keyVault]" + }, + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } + ] + }, + "dependsOn": [ + "keyVault" + ] + }, + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" } } - ] + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } } } + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", + "getStoragePrivateEndpointConnections", + "keyVault" ] - } - } - ], - "concurrency": 1, - "variables": { - "scopesArray": { - "type": "Array" - }, - "exportName": { - "type": "String" - }, - "exportScope": { - "type": "String" - }, - "exportScopeType": { - "type": "String" - }, - "storageAccountId": { - "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" - }, - "finOpsHub": { - "type": "String", - "defaultValue": "[parameters('hubName')]" - }, - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[parameters('configContainerName')]" - } - } - }, - "dependsOn": [ - "dataset_config" - ], - "metadata": { - "description": "Creates Cost Management exports for supported scopes." - } - }, - "pipeline_ExecuteExportsETL": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ExecuteETL', variables('safeExportContainerName')))]", - "properties": { - "activities": [ - { - "name": "Wait", - "description": "Files may not be available immediately after being created.", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 60 - } - }, - { - "name": "Read Manifest", - "description": "Load the export manifest to determine the scope, dataset, and date range.", - "type": "Lookup", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Completed" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "formatSettings": { - "type": "JsonReadSettings" + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } } }, - "dataset": { - "referenceName": "manifest", - "type": "DatasetReference", + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections", + "keyVault" + ] + }, + "getStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", "parameters": { - "fileName": { - "value": "@pipeline().parameters.fileName", - "type": "Expression" + "storageAccountName": { + "value": "[parameters('app').storage]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } }, - "folderPath": { - "value": "@pipeline().parameters.folderPath", - "type": "Expression" + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } } } - } - } - }, - { - "name": "Set Has No Rows", - "description": "Check the row count ", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "hasNoRows", - "value": { - "value": "@or(equals(activity('Read Manifest').output.firstRow.blobCount, null), equals(activity('Read Manifest').output.firstRow.blobCount, 0))", - "type": "Expression" - } - } - }, - { - "name": "Set Export Dataset Type", - "description": "Save the dataset type from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "exportDatasetType", - "value": { - "value": "@activity('Read Manifest').output.firstRow.exportConfig.type", - "type": "Expression" - } - } - }, - { - "name": "Set MCA Column", - "description": "Determines if the dataset schema has channel-specific columns and saves the column name that only exists in MCA to determine if it is an MCA dataset.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "mcaColumnToCheck", - "value": { - "value": "@if(contains(createArray('pricesheet', 'reservationtransactions'), toLower(variables('exportDatasetType'))), 'BillingProfileId', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Net Savings', null))", - "type": "Expression" - } - } - }, - { - "name": "Set Export Dataset Version", - "description": "Save the dataset version from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "exportDatasetVersion", - "value": { - "value": "@activity('Read Manifest').output.firstRow.exportConfig.dataVersion", - "type": "Expression" - } - } - }, - { - "name": "Detect Channel", - "description": "Determines what channel this export is from. Switch statement handles the different file types if the mcaColumnToCheck variable is set.", - "type": "Switch", - "dependsOn": [ - { - "activity": "Set Has No Rows", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set MCA Column", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Export Dataset Version", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "on": { - "value": "@if(or(empty(variables('mcaColumnToCheck')), variables('hasNoRows')), 'ignore', last(array(split(activity('Read Manifest').output.firstRow.blobs[0].blobName, '.'))))", - "type": "Expression" }, - "cases": [ - { - "value": "csv", - "activities": [ + "dependsOn": [ + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", + "stopTriggers", + "storageAccount" + ] + }, + "approveStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ { - "name": "Check for MCA Column in CSV", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getStoragePrivateEndpointConnections", + "storageAccount" + ] + }, + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('app').dataFactory]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } } }, - "dataset": { - "referenceName": "[variables('safeExportContainerName')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - { - "name": "Set Schema File with Channel in CSV", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in CSV", - "dependencyConditions": [ - "Succeeded" - ] + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in CSV').output, 'firstRow'), contains(activity('Check for MCA Column in CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } - } - ] - }, - { - "value": "gz", - "activities": [ - { - "name": "Check for MCA Column in Gzip CSV", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" - } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "dataset": { - "referenceName": "[format('{0}_gzip', variables('safeExportContainerName'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" - } - } + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } }, - { - "name": "Set Schema File with Channel in Gzip CSV", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in Gzip CSV", - "dependencyConditions": [ - "Succeeded" - ] + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Gzip CSV').output, 'firstRow'), contains(activity('Check for MCA Column in Gzip CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } } - ] - }, - { - "value": "parquet", - "activities": [ - { - "name": "Check for MCA Column in Parquet", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "ParquetSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" - } - }, - "dataset": { - "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" - } - } - } + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" }, - { - "name": "Set Schema File with Channel for Parquet", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in Parquet", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Parquet').output, 'firstRow'), contains(activity('Check for MCA Column in Parquet').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} } - } - } - ] - } - ], - "defaultActivities": [ - { - "name": "Set Schema File", - "type": "SetVariable", - "dependsOn": [], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), '.json'))", - "type": "Expression" + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } + }, + "dependsOn": [ + "appTelemetry", + "dataFactory", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" ] } }, - { - "name": "Set Scope", - "description": "Save the scope from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] + "outputs": { + "dataFactoryId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Data Factory instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" + }, + "keyVaultId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Key Vault instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + }, + "triggerManagerIdentityName": { + "type": "string", + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + } + } + } + } + }, + "timeZones": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.ManagedExports_TimeZones", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('app').hub.location]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "6509457716792571662" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "scope", - "value": { - "value": "@split(toLower(activity('Read Manifest').output.firstRow.exportConfig.resourceId), '/providers/microsoft.costmanagement/exports/')[0]", - "type": "Expression" + "timezoneobject": { + "type": "object", + "defaultValue": { + "australiaeast": "AUS Eastern Standard Time", + "australiacentral": "AUS Eastern Standard Time", + "australiacentral2": "AUS Eastern Standard Time", + "australiasoutheast": "AUS Eastern Standard Time", + "brazilsouth": "E. South America Standard Time", + "canadacentral": "Central Standard Time", + "canadaeast": "Eastern Standard Time", + "centralindia": "India Standard Time", + "centralus": "Central Standard Time", + "eastasia": "China Standard Time", + "eastus": "Eastern Standard Time", + "eastus2": "Eastern Standard Time", + "francecentral": "W. Europe Standard Time", + "germanynorth": "W. Europe Standard Time", + "germanywestcentral": "W. Europe Standard Time", + "japaneast": "Japan Standard Time", + "japanwest": "Japan Standard Time", + "koreacentral": "Korea Standard Time", + "koreasouth": "Korea Standard Time", + "northcentralus": "Central Standard Time", + "northeurope": "GMT Standard Time", + "norwayeast": "W. Europe Standard Time", + "norwaywest": "W. Europe Standard Time", + "southcentralus": "Central Standard Time", + "southindia": "India Standard Time", + "southeastasia": "Singapore Standard Time", + "switzerlandnorth": "W. Europe Standard Time", + "switzerlandwest": "W. Europe Standard Time", + "uksouth": "GMT Standard Time", + "ukwest": "GMT Standard Time", + "westcentralus": "Central Standard Time", + "westeurope": "W. Europe Standard Time", + "westindia": "India Standard Time", + "westus": "Pacific Standard Time", + "westus2": "Pacific Standard Time" } + }, + "utchrs": { + "type": "string", + "defaultValue": "[utcNow('hh')]" + }, + "utcmins": { + "type": "string", + "defaultValue": "[utcNow('mm')]" + }, + "utcsecs": { + "type": "string", + "defaultValue": "[utcNow('ss')]" } }, - { - "name": "Set Date", - "description": "Save the exported month from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] + "variables": { + "loc": "[toLower(replace(parameters('location'), ' ', ''))]", + "timezone": "[coalesce(tryGet(parameters('timezoneobject'), variables('loc')), 'Universal Coordinated Time')]" + }, + "resources": [], + "outputs": { + "AzureRegion": { + "type": "string", + "value": "[parameters('location')]" + }, + "Timezone": { + "type": "string", + "value": "[variables('timezone')]" + }, + "UtcHours": { + "type": "string", + "value": "[parameters('utchrs')]" + }, + "UtcMinutes": { + "type": "string", + "value": "[parameters('utcmins')]" + }, + "UtcSeconds": { + "type": "string", + "value": "[parameters('utcsecs')]" + } + } + } + } + }, + "trigger_SettingsUpdated": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_SettingsUpdatedTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('app').dataFactory]" + }, + "triggerName": { + "value": "[format('{0}_SettingsUpdated', variables('CONFIG'))]" + }, + "pipelineName": { + "value": "[format('{0}_ConfigureExports', variables('CONFIG'))]" + }, + "pipelineParameters": { + "value": {} + }, + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "storageContainer": { + "value": "[variables('CONFIG')]" + }, + "storagePathEndsWith": { + "value": "settings.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14264521107451792604" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." + } + }, + "triggerName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory trigger to create or update." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storageContainer": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storagePathStartsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "date", - "value": { - "value": "@replace(substring(activity('Read Manifest').output.firstRow.runInfo.startDate, 0, 7), '-', '')", - "type": "Expression" + "storagePathEndsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } + }, + "pipelineName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } + }, + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." } } }, - { - "name": "Failed to Read Manifest", - "type": "Fail", - "dependsOn": [ - { - "activity": "Set Date", - "dependencyConditions": [ - "Failed" - ] - }, - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Failed" - ] - }, - { - "activity": "Set Scope", - "dependencyConditions": [ - "Failed" - ] - }, - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Failed" - ] - }, - { - "activity": "Set Export Dataset Version", - "dependencyConditions": [ - "Failed" - ] - }, - { - "activity": "Detect Channel", - "dependencyConditions": [ - "Failed" - ] + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" + }, + "parameters": "[parameters('pipelineParameters')]" + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] + } } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Failed to read the manifest file for this export run. Manifest path: ', pipeline().parameters.folderPath)", - "type": "Expression" - }, - "errorCode": "ManifestReadFailed" } - }, - { - "name": "Check Schema", - "description": "Verify that the schema file exists in storage.", - "type": "GetMetadata", - "dependsOn": [ - { - "activity": "Set Scope", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Date", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Detect Channel", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + ] + } + }, + "dependsOn": [ + "dataFactory::pipeline_ConfigureExports" + ] + } + } + } + }, + "dependsOn": [ + "cmExports" + ] + }, + "analytics": { + "condition": "[or(variables('useFabric'), variables('useAzureDataExplorer'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'Analytics')]" + }, + "fabricQueryUri": { + "value": "[parameters('fabricQueryUri')]" + }, + "fabricCapacityUnits": { + "value": "[parameters('fabricCapacityUnits')]" + }, + "clusterName": { + "value": "[parameters('dataExplorerName')]" + }, + "clusterSku": { + "value": "[parameters('dataExplorerSku')]" + }, + "clusterCapacity": { + "value": "[parameters('dataExplorerCapacity')]" + }, + "rawRetentionInDays": { + "value": "[parameters('dataExplorerRawRetentionInDays')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "16399190021391778181" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('schemaFile')", - "type": "Expression" - }, - "folderPath": "[format('{0}/schemas', parameters('configContainerName'))]" - } - }, - "fieldList": [ - "exists" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "privateRoutingForLinkedServices": { + "parameters": [ + { + "$ref": "#/definitions/_1.HubProperties", + "name": "hub" } + ], + "output": { + "type": "object", + "value": "[if(parameters('hub').options.privateRouting, createObject('connectVia', createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference')), createObject())]" }, - { - "name": "Schema Not Found", - "type": "Fail", - "dependsOn": [ - { - "activity": "Check Schema", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('The ', variables('schemaFile'), ' schema mapping file was not found. Please confirm version ', variables('exportDatasetVersion'), ' of the ', variables('exportDatasetType'), ' dataset is supported by this version of FinOps hubs. You may need to upgrade to a newer release. To add support for another dataset, you can create a custom mapping file.')", - "type": "Expression" - }, - "errorCode": "SchemaNotFound" + "metadata": { + "description": "Returns an object that represents the properties needed to enable private routing for linked services. Use property expansion (`...value`) to apply to a linkedServices resource.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + }, + "clusterName": { + "type": "string", + "defaultValue": "", + "maxLength": 22, + "metadata": { + "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics. If empty, Azure Data Explorer will not be deployed. Required to use with Power BI if you have more than $2-5M/mo in costs being monitored. Default: \"\" (do not use)." + } + }, + "clusterSku": { + "type": "string", + "defaultValue": "Dev(No SLA)_Standard_E2a_v4", + "allowedValues": [ + "Dev(No SLA)_Standard_E2a_v4", + "Dev(No SLA)_Standard_D11_v2", + "Standard_D11_v2", + "Standard_D12_v2", + "Standard_D13_v2", + "Standard_D14_v2", + "Standard_D16d_v5", + "Standard_D32d_v4", + "Standard_D32d_v5", + "Standard_DS13_v2+1TB_PS", + "Standard_DS13_v2+2TB_PS", + "Standard_DS14_v2+3TB_PS", + "Standard_DS14_v2+4TB_PS", + "Standard_E2a_v4", + "Standard_E2ads_v5", + "Standard_E2d_v4", + "Standard_E2d_v5", + "Standard_E4a_v4", + "Standard_E4ads_v5", + "Standard_E4d_v4", + "Standard_E4d_v5", + "Standard_E8a_v4", + "Standard_E8ads_v5", + "Standard_E8as_v4+1TB_PS", + "Standard_E8as_v4+2TB_PS", + "Standard_E8as_v5+1TB_PS", + "Standard_E8as_v5+2TB_PS", + "Standard_E8d_v4", + "Standard_E8d_v5", + "Standard_E8s_v4+1TB_PS", + "Standard_E8s_v4+2TB_PS", + "Standard_E8s_v5+1TB_PS", + "Standard_E8s_v5+2TB_PS", + "Standard_E16a_v4", + "Standard_E16ads_v5", + "Standard_E16as_v4+3TB_PS", + "Standard_E16as_v4+4TB_PS", + "Standard_E16as_v5+3TB_PS", + "Standard_E16as_v5+4TB_PS", + "Standard_E16d_v4", + "Standard_E16d_v5", + "Standard_E16s_v4+3TB_PS", + "Standard_E16s_v4+4TB_PS", + "Standard_E16s_v5+3TB_PS", + "Standard_E16s_v5+4TB_PS", + "Standard_E64i_v3", + "Standard_E80ids_v4", + "Standard_EC8ads_v5", + "Standard_EC8as_v5+1TB_PS", + "Standard_EC8as_v5+2TB_PS", + "Standard_EC16ads_v5", + "Standard_EC16as_v5+3TB_PS", + "Standard_EC16as_v5+4TB_PS", + "Standard_L4s", + "Standard_L8as_v3", + "Standard_L8s", + "Standard_L8s_v2", + "Standard_L8s_v3", + "Standard_L16as_v3", + "Standard_L16s", + "Standard_L16s_v2", + "Standard_L16s_v3", + "Standard_L32as_v3", + "Standard_L32s_v3" + ], + "metadata": { + "description": "Optional. Name of the Azure Data Explorer SKU. Default: \"Dev(No SLA)_Standard_E2a_v4\"." + } + }, + "clusterCapacity": { + "type": "int", + "defaultValue": 1, + "minValue": 1, + "maxValue": 1000, + "metadata": { + "description": "Optional. Number of nodes to use in the cluster. Allowed values: 1 for the Basic SKU tier and 2-1000 for Standard. Default: 1 for dev/test SKUs, 2 for standard SKUs." + } + }, + "fabricQueryUri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Microsoft Fabric eventhouse query URI. Default: \"\" (do not use)." + } + }, + "fabricCapacityUnits": { + "type": "int", + "defaultValue": 2, + "minValue": 1, + "maxValue": 2048, + "metadata": { + "description": "Optional. Number of capacity units for the Microsoft Fabric capacity. This is the number in your Fabric SKU (e.g., Trial = 1, F2 = 2, F64 = 64). This is used to manage parallelization in data pipelines. If you change capacity, please redeploy the template. Allowed values: 1 for the Fabric trial and 2-2048 based on the assigned Fabric capacity (e.g., F2-F2048). Default: 2." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "rawRetentionInDays": { + "type": "int", + "metadata": { + "description": "Required. Number of days of data to retain in the Data Explorer *_raw tables." + } + } + }, + "variables": { + "$fxv#0": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_1(id: string) {\n dynamic({\n \"arizeai.observabilityeval/organizations\": { \"SingularDisplayName\": \"Azure Native Arize AI Cloud Service\" }\n ,\"astronomer.astro/organizations\": { \"SingularDisplayName\": \"Astro Organization\" }\n ,\"citrix.services/xenappessentials\": { \"SingularDisplayName\": \"Citrix Virtual Apps Essentials\" }\n ,\"citrix.services/xendesktopessentials\": { \"SingularDisplayName\": \"Citrix Virtual Desktops Essentials\" }\n ,\"commvault.contentstore/cloudaccounts\": { \"SingularDisplayName\": \"Commvault Cloud Account\" }\n ,\"commvault.contentstore/cloudaccounts/plans\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts plan\" }\n ,\"commvault.contentstore/cloudaccounts/protectiongroups\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection group\" }\n ,\"commvault.contentstore/cloudaccounts/protectiongroups/protecteditems\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection groups protected item\" }\n ,\"commvault.contentstore/cloudaccounts/storages\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts storage\" }\n ,\"dell.storage/filesystems\": { \"SingularDisplayName\": \"Dell PowerScale\" }\n ,\"dynatrace.observability/monitors\": { \"SingularDisplayName\": \"Dynatrace\" }\n ,\"github.network/networksettings\": { \"SingularDisplayName\": \"GitHub.Network network setting\" }\n ,\"informatica.datamanagement/organizations\": { \"SingularDisplayName\": \"Informatica Organization\" }\n ,\"lambdatest.hyperexecute/organizations\": { \"SingularDisplayName\": \"Azure Native LambdaTest - HyperExecute Cloud Service\" }\n ,\"microsoft.aad/domainservices\": { \"SingularDisplayName\": \"Microsoft Entra Domain Services\" }\n ,\"microsoft.aadiam/diagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.aadiam diagnostic setting\" }\n ,\"microsoft.aadiam/privatelinkforazuread\": { \"SingularDisplayName\": \"Private Link for Microsoft Entra ID\" }\n ,\"microsoft.advisor/advisorscore\": { \"SingularDisplayName\": \"Microsoft.Advisor advisor score\" }\n ,\"microsoft.advisor/assessments\": { \"SingularDisplayName\": \"Microsoft.Advisor assessment\" }\n ,\"microsoft.advisor/configurations\": { \"SingularDisplayName\": \"Microsoft.Advisor configuration\" }\n ,\"microsoft.advisor/generaterecommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor generate recommendation\" }\n ,\"microsoft.advisor/metadata\": { \"SingularDisplayName\": \"Microsoft.Advisor metadata\" }\n ,\"microsoft.advisor/recommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendation\" }\n ,\"microsoft.advisor/recommendations/suppressions\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendations suppression\" }\n ,\"microsoft.advisor/resiliencyreviews\": { \"SingularDisplayName\": \"Microsoft.Advisor resiliency review\" }\n ,\"microsoft.agfoodplatform/farmbeats\": { \"SingularDisplayName\": \"Azure Data Manager for Agriculture\" }\n ,\"microsoft.agfoodplatform/farmbeatsextensiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats extension definition\" }\n ,\"microsoft.agfoodplatform/farmbeatssolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats solution definition\" }\n ,\"microsoft.agricultureplatform/agriservices\": { \"SingularDisplayName\": \"Agriculture data solutions\" }\n ,\"microsoft.akshybrid/agentpools\": { \"SingularDisplayName\": \"Microsoft.AksHybrid agent pool\" }\n ,\"microsoft.akshybrid/provisionedclusters\": { \"SingularDisplayName\": \"Microsoft.AksHybrid provisioned cluster\" }\n ,\"microsoft.akshybrid/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.AksHybrid upgrade profile\" }\n ,\"microsoft.alertsmanagement/actionrules\": { \"SingularDisplayName\": \"Alert processing rule\" }\n ,\"microsoft.alertsmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alert\" }\n ,\"microsoft.alertsmanagement/alerts/enrichments\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alerts enrichment\" }\n ,\"microsoft.alertsmanagement/prometheusrulegroups\": { \"SingularDisplayName\": \"Prometheus rule group\" }\n ,\"microsoft.alertsmanagement/smartdetectoralertrules\": { \"SingularDisplayName\": \"Smart detector alert rule\" }\n ,\"microsoft.alertsmanagement/smartgroups\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement smart group\" }\n ,\"microsoft.alertsmanagement/tenantactivitylogalerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement tenant activity log alert\" }\n ,\"microsoft.all/arcvirtualmachines\": { \"SingularDisplayName\": \"Azure Arc virtual machine\" }\n ,\"microsoft.all/hcivirtualmachines\": { \"SingularDisplayName\": \"Azure Local Virtual Machine - Azure Arc\" }\n ,\"microsoft.all/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.analysisservices/servers\": { \"SingularDisplayName\": \"Analysis Services server\" }\n ,\"microsoft.anybuild/clusters\": { \"SingularDisplayName\": \"AnyBuild cluster\" }\n ,\"microsoft.apicenter/deletedservices\": { \"SingularDisplayName\": \"Microsoft.ApiCenter deleted service\" }\n ,\"microsoft.apicenter/services\": { \"SingularDisplayName\": \"API Center\" }\n ,\"microsoft.apicenter/services/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.apimanagement/gateways\": { \"SingularDisplayName\": \"API Management gateway\" }\n ,\"microsoft.apimanagement/gateways/configconnections\": { \"SingularDisplayName\": \"Microsoft.ApiManagement gateways config connection\" }\n ,\"microsoft.apimanagement/service\": { \"SingularDisplayName\": \"API Management service\" }\n ,\"microsoft.apimanagement/service/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.apisecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.ApiSecurity defender setting\" }\n ,\"microsoft.app/agents\": { \"SingularDisplayName\": \"SRE Agent\" }\n ,\"microsoft.app/builders\": { \"SingularDisplayName\": \"Microsoft.App builder\" }\n ,\"microsoft.app/builders/builds\": { \"SingularDisplayName\": \"Microsoft.App builders build\" }\n ,\"microsoft.app/connectedenvironments\": { \"SingularDisplayName\": \"Container Apps Connected Environment\" }\n ,\"microsoft.app/containerapps\": { \"SingularDisplayName\": \"Container App\" }\n ,\"microsoft.app/jobs\": { \"SingularDisplayName\": \"Container App Job\" }\n ,\"microsoft.app/logicapps\": { \"SingularDisplayName\": \"Logic app\" }\n ,\"microsoft.app/logicapps/workflows\": { \"SingularDisplayName\": \"Logic app workflow\" }\n ,\"microsoft.app/managedenvironments\": { \"SingularDisplayName\": \"Container Apps Environment\" }\n ,\"microsoft.app/sessionpools\": { \"SingularDisplayName\": \"Container App Session Pool\" }\n ,\"microsoft.app/spaces\": { \"SingularDisplayName\": \"App Space\" }\n ,\"microsoft.appassessment/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate project\" }\n ,\"microsoft.appassessment/migrateprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessment\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedapplications\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed application\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed machine\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/machinestoassess\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments machines to asses\" }\n ,\"microsoft.appassessment/migrateprojects/sites\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects site\" }\n ,\"microsoft.appassessment/migrateprojects/sites/applianceconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects sites appliance configuration\" }\n ,\"microsoft.appcomplianceautomation/reports\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation report\" }\n ,\"microsoft.appcomplianceautomation/reports/evidences\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports evidence\" }\n ,\"microsoft.appcomplianceautomation/reports/scopingconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports scoping configuration\" }\n ,\"microsoft.appcomplianceautomation/reports/snapshots\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshot\" }\n ,\"microsoft.appcomplianceautomation/reports/snapshots/controls\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshots control\" }\n ,\"microsoft.appcomplianceautomation/reports/webhooks\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports webhook\" }\n ,\"microsoft.appconfiguration/configurationstores\": { \"SingularDisplayName\": \"App Configuration\" }\n ,\"microsoft.applicationmigration/discoveryhubs\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hub\" }\n ,\"microsoft.applicationmigration/discoveryhubs/applications\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs application\" }\n ,\"microsoft.applicationmigration/discoveryhubs/applications/members\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs applications member\" }\n ,\"microsoft.applicationmigration/pgsqlsites\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsite\" }\n ,\"microsoft.applicationmigration/pgsqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites agent\" }\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqldatabases\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqldatabase\" }\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqlinstances\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqlinstance\" }\n ,\"microsoft.appplatform/spring\": { \"SingularDisplayName\": \"Azure Spring Apps\" }\n ,\"microsoft.appsecurity/appprotectmanagedrulesetmanifests\": { \"SingularDisplayName\": \"Microsoft.AppSecurity app protect managed rule set manifest\" }\n ,\"microsoft.appsecurity/policies\": { \"SingularDisplayName\": \"App Protect Policy\" }\n ,\"microsoft.arc/all\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\n ,\"microsoft.arc/allfairfax\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\n ,\"microsoft.arc/kubernetesresources\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\n ,\"microsoft.arc/kubernetesresourcesfairfax\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\n ,\"microsoft.arcnetworking/arcnwloadbalancers\": { \"SingularDisplayName\": \"Microsoft.ArcNetworking arc nw load balancer\" }\n ,\"microsoft.aszlabhardware/labservers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware labserver\" }\n ,\"microsoft.aszlabhardware/reservations\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservation\" }\n ,\"microsoft.aszlabhardware/reservations/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservations server\" }\n ,\"microsoft.aszlabhardware/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware server\" }\n ,\"microsoft.attestation/attestationproviders\": { \"SingularDisplayName\": \"Attestation provider\" }\n ,\"microsoft.authorization/accessreviewhistorydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review history definition\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definition\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instance\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances/decisions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instances decision\" }\n ,\"microsoft.authorization/accessreviewschedulesettings\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule setting\" }\n ,\"microsoft.authorization/datapolicymanifests\": { \"SingularDisplayName\": \"Microsoft.Authorization data policy manifest\" }\n ,\"microsoft.authorization/denyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization deny assignment\" }\n ,\"microsoft.authorization/locks\": { \"SingularDisplayName\": \"Microsoft.Authorization lock\" }\n ,\"microsoft.authorization/policyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization policy assignment\" }\n ,\"microsoft.authorization/policydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definition\" }\n ,\"microsoft.authorization/policydefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definitions version\" }\n ,\"microsoft.authorization/policyexemptions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy exemption\" }\n ,\"microsoft.authorization/policysetdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definition\" }\n ,\"microsoft.authorization/policysetdefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definitions version\" }\n ,\"microsoft.authorization/privatelinkassociations\": { \"SingularDisplayName\": \"Microsoft.Authorization private link association\" }\n ,\"microsoft.authorization/provideroperations\": { \"SingularDisplayName\": \"Microsoft.Authorization provider operation\" }\n ,\"microsoft.authorization/resourcemanagementprivatelinks\": { \"SingularDisplayName\": \"Resource management private link\" }\n ,\"microsoft.authorization/roleassignmentapprovals\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approval\" }\n ,\"microsoft.authorization/roleassignmentapprovals/stages\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approvals stage\" }\n ,\"microsoft.authorization/roleassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment\" }\n ,\"microsoft.authorization/roleassignmentscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule instance\" }\n ,\"microsoft.authorization/roleassignmentschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule request\" }\n ,\"microsoft.authorization/roleassignmentschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule\" }\n ,\"microsoft.authorization/roledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role definition\" }\n ,\"microsoft.authorization/roleeligibilityscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule instance\" }\n ,\"microsoft.authorization/roleeligibilityschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule request\" }\n ,\"microsoft.authorization/roleeligibilityschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule\" }\n ,\"microsoft.authorization/rolemanagementalertconfigurations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert configuration\" }\n ,\"microsoft.authorization/rolemanagementalertdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert definition\" }\n ,\"microsoft.authorization/rolemanagementalertoperations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert operation\" }\n ,\"microsoft.authorization/rolemanagementalerts\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert\" }\n ,\"microsoft.authorization/rolemanagementalerts/alertincidents\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alerts alert incident\" }\n ,\"microsoft.authorization/rolemanagementpolicies\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy\" }\n ,\"microsoft.authorization/rolemanagementpolicyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy assignment\" }\n ,\"microsoft.automanage/bestpractices\": { \"SingularDisplayName\": \"Microsoft.Automanage best practice\" }\n ,\"microsoft.automanage/bestpractices/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage best practices version\" }\n ,\"microsoft.automanage/configurationprofileassignments\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignment\" }\n ,\"microsoft.automanage/configurationprofileassignments/reports\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignments report\" }\n ,\"microsoft.automanage/configurationprofiles\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile\" }\n ,\"microsoft.automanage/configurationprofiles/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profiles version\" }\n ,\"microsoft.automanage/serviceprincipals\": { \"SingularDisplayName\": \"ServicePrincipals\" }\n ,\"microsoft.automation/automationaccounts\": { \"SingularDisplayName\": \"Automation account\" }\n ,\"microsoft.automation/automationaccounts/hybridrunbookworkergroups\": { \"SingularDisplayName\": \"Automation hybrid worker group\" }\n ,\"microsoft.automation/automationaccounts/runbooks\": { \"SingularDisplayName\": \"Automation runbook\" }\n ,\"microsoft.autonomousdevelopmentplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform account\" }\n ,\"microsoft.autonomousdevelopmentplatform/accounts/datapools\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform accounts data pool\" }\n ,\"microsoft.autonomousdevelopmentplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform workspace\" }\n ,\"microsoft.avs/privateclouds\": { \"SingularDisplayName\": \"Azure VMware Solution private cloud\" }\n ,\"microsoft.awsconnector/accessanalyzeranalyzers\": { \"SingularDisplayName\": \"Access Analyzer Analyzer\" }\n ,\"microsoft.awsconnector/acmcertificatesummaries\": { \"SingularDisplayName\": \"ACM Certificate Summary\" }\n ,\"microsoft.awsconnector/apigatewayrestapis\": { \"SingularDisplayName\": \"Api Gateway Rest Api\" }\n ,\"microsoft.awsconnector/apigatewaystages\": { \"SingularDisplayName\": \"Api Gateway Stage\" }\n ,\"microsoft.awsconnector/applicationautoscalingscalabletargets\": { \"SingularDisplayName\": \"Application Auto Scaling Scalable Target\" }\n ,\"microsoft.awsconnector/appsyncgraphqlapis\": { \"SingularDisplayName\": \"App Sync Graphql Api\" }\n ,\"microsoft.awsconnector/autoscalingautoscalinggroups\": { \"SingularDisplayName\": \"Auto Scaling Auto Scaling Group\" }\n ,\"microsoft.awsconnector/cloudformationstacks\": { \"SingularDisplayName\": \"Cloud Formation Stack\" }\n ,\"microsoft.awsconnector/cloudformationstacksets\": { \"SingularDisplayName\": \"Cloud Formation Stack Set\" }\n ,\"microsoft.awsconnector/cloudfrontdistributions\": { \"SingularDisplayName\": \"Cloud Front Distribution\" }\n ,\"microsoft.awsconnector/cloudtrailtrails\": { \"SingularDisplayName\": \"Cloud Trail Trail\" }\n ,\"microsoft.awsconnector/cloudwatchalarms\": { \"SingularDisplayName\": \"Cloud Watch Alarm\" }\n ,\"microsoft.awsconnector/codebuildprojects\": { \"SingularDisplayName\": \"Code Build Project\" }\n ,\"microsoft.awsconnector/codebuildsourcecredentialsinfos\": { \"SingularDisplayName\": \"Code Build Source Credentials Info\" }\n ,\"microsoft.awsconnector/configserviceconfigurationrecorders\": { \"SingularDisplayName\": \"Config Service Configuration Recorder\" }\n ,\"microsoft.awsconnector/configserviceconfigurationrecorderstatuses\": { \"SingularDisplayName\": \"Config Service Configuration Recorder Status\" }\n ,\"microsoft.awsconnector/configservicedeliverychannels\": { \"SingularDisplayName\": \"Config Service Delivery Channel\" }\n ,\"microsoft.awsconnector/databasemigrationservicereplicationinstances\": { \"SingularDisplayName\": \"Database Migration Service Replication Instance\" }\n ,\"microsoft.awsconnector/daxclusters\": { \"SingularDisplayName\": \"DAX Cluster\" }\n ,\"microsoft.awsconnector/dynamodbcontinuousbackupsdescriptions\": { \"SingularDisplayName\": \"Dynamo DB Continuous Backups Description\" }\n ,\"microsoft.awsconnector/dynamodbtables\": { \"SingularDisplayName\": \"Dynamo DB Table\" }\n ,\"microsoft.awsconnector/ec2accountattributes\": { \"SingularDisplayName\": \"EC2 Account Attribute\" }\n ,\"microsoft.awsconnector/ec2addresses\": { \"SingularDisplayName\": \"EC2 Address\" }\n ,\"microsoft.awsconnector/ec2flowlogs\": { \"SingularDisplayName\": \"EC2 Flow Log\" }\n ,\"microsoft.awsconnector/ec2images\": { \"SingularDisplayName\": \"EC2 Image\" }\n ,\"microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\n ,\"microsoft.awsconnector/ec2instancestatuses\": { \"SingularDisplayName\": \"EC2 Instance Status\" }\n ,\"microsoft.awsconnector/ec2ipams\": { \"SingularDisplayName\": \"EC2 Ipam\" }\n ,\"microsoft.awsconnector/ec2keypairs\": { \"SingularDisplayName\": \"EC2 Key Pair\" }\n ,\"microsoft.awsconnector/ec2networkacls\": { \"SingularDisplayName\": \"EC2 Network Acl\" }\n ,\"microsoft.awsconnector/ec2networkinterfaces\": { \"SingularDisplayName\": \"EC2 Network Interface\" }\n ,\"microsoft.awsconnector/ec2routetables\": { \"SingularDisplayName\": \"EC2 Route Table\" }\n ,\"microsoft.awsconnector/ec2securitygroups\": { \"SingularDisplayName\": \"EC2 Security Group\" }\n ,\"microsoft.awsconnector/ec2snapshots\": { \"SingularDisplayName\": \"EC2 Snapshot\" }\n ,\"microsoft.awsconnector/ec2subnets\": { \"SingularDisplayName\": \"EC2 Subnet\" }\n ,\"microsoft.awsconnector/ec2volumes\": { \"SingularDisplayName\": \"EC2 Volume\" }\n ,\"microsoft.awsconnector/ec2vpcendpoints\": { \"SingularDisplayName\": \"EC2 VPCEndpoint\" }\n ,\"microsoft.awsconnector/ec2vpcpeeringconnections\": { \"SingularDisplayName\": \"EC2 VPCPeering Connection\" }\n ,\"microsoft.awsconnector/ec2vpcs\": { \"SingularDisplayName\": \"EC2 VPC\" }\n ,\"microsoft.awsconnector/ecrimagedetails\": { \"SingularDisplayName\": \"ECR Image Detail\" }\n ,\"microsoft.awsconnector/ecrrepositories\": { \"SingularDisplayName\": \"ECR Repository\" }\n ,\"microsoft.awsconnector/ecsclusters\": { \"SingularDisplayName\": \"ECS Cluster\" }\n ,\"microsoft.awsconnector/ecsservices\": { \"SingularDisplayName\": \"ECS Service\" }\n ,\"microsoft.awsconnector/ecstaskdefinitions\": { \"SingularDisplayName\": \"ECS Task Definition\" }\n ,\"microsoft.awsconnector/efsfilesystems\": { \"SingularDisplayName\": \"EFS File System\" }\n ,\"microsoft.awsconnector/efsmounttargets\": { \"SingularDisplayName\": \"EFS Mount Target\" }\n ,\"microsoft.awsconnector/eksnodegroups\": { \"SingularDisplayName\": \"EKS Nodegroup\" }\n ,\"microsoft.awsconnector/elasticbeanstalkapplications\": { \"SingularDisplayName\": \"Elastic Beanstalk Application\" }\n ,\"microsoft.awsconnector/elasticbeanstalkconfigurationtemplates\": { \"SingularDisplayName\": \"Elastic Beanstalk Configuration Template\" }\n ,\"microsoft.awsconnector/elasticbeanstalkenvironments\": { \"SingularDisplayName\": \"Elastic Beanstalk Environment\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2listeners\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Listener\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2loadbalancers\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Load Balancer\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2targetgroups\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Target Group\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2targethealthdescriptions\": { \"SingularDisplayName\": \"Elastic Load Balancing v2 Target Health Description\" }\n ,\"microsoft.awsconnector/elasticsearchdomains\": { \"SingularDisplayName\": \"Elasticsearch Domain\" }\n ,\"microsoft.awsconnector/emrclusters\": { \"SingularDisplayName\": \"EMR Cluster\" }\n ,\"microsoft.awsconnector/guarddutydetectors\": { \"SingularDisplayName\": \"Guard Duty Detector\" }\n ,\"microsoft.awsconnector/iamaccesskeylastuseds\": { \"SingularDisplayName\": \"IAM Access Key Last Used\" }\n ,\"microsoft.awsconnector/iamaccesskeymetadata\": { \"SingularDisplayName\": \"IAM Access Key Metadata\" }\n ,\"microsoft.awsconnector/iamgroups\": { \"SingularDisplayName\": \"IAM Group\" }\n ,\"microsoft.awsconnector/iaminstanceprofiles\": { \"SingularDisplayName\": \"IAM Instance Profile\" }\n ,\"microsoft.awsconnector/iammanagedpolicies\": { \"SingularDisplayName\": \"IAM Managed Policy\" }\n ,\"microsoft.awsconnector/iammfadevices\": { \"SingularDisplayName\": \"IAM MFADevice\" }\n ,\"microsoft.awsconnector/iampasswordpolicies\": { \"SingularDisplayName\": \"IAM Password Policy\" }\n ,\"microsoft.awsconnector/iampolicyversions\": { \"SingularDisplayName\": \"IAM Policy Version\" }\n ,\"microsoft.awsconnector/iamroles\": { \"SingularDisplayName\": \"IAM Role\" }\n ,\"microsoft.awsconnector/iamservercertificates\": { \"SingularDisplayName\": \"IAM Server Certificate\" }\n ,\"microsoft.awsconnector/iamuserpolicies\": { \"SingularDisplayName\": \"IAM User Policy\" }\n ,\"microsoft.awsconnector/iamvirtualmfadevices\": { \"SingularDisplayName\": \"IAM Virtual MFADevice\" }\n ,\"microsoft.awsconnector/kmsaliases\": { \"SingularDisplayName\": \"KMS Alias\" }\n ,\"microsoft.awsconnector/kmskeys\": { \"SingularDisplayName\": \"KMS Key\" }\n ,\"microsoft.awsconnector/lambdafunctioncodelocations\": { \"SingularDisplayName\": \"Lambda Function Code Location\" }\n ,\"microsoft.awsconnector/lambdafunctionconfigurations\": { \"SingularDisplayName\": \"Microsoft.AwsConnector lambda function configuration\" }\n ,\"microsoft.awsconnector/lambdafunctions\": { \"SingularDisplayName\": \"Lambda Function\" }\n ,\"microsoft.awsconnector/licensemanagerlicenses\": { \"SingularDisplayName\": \"License Manager License\" }\n ,\"microsoft.awsconnector/lightsailbuckets\": { \"SingularDisplayName\": \"Lightsail Bucket\" }\n ,\"microsoft.awsconnector/lightsailinstances\": { \"SingularDisplayName\": \"Lightsail Instance\" }\n ,\"microsoft.awsconnector/logsloggroups\": { \"SingularDisplayName\": \"Logs Log Group\" }\n ,\"microsoft.awsconnector/logslogstreams\": { \"SingularDisplayName\": \"Logs Log Stream\" }\n ,\"microsoft.awsconnector/logsmetricfilters\": { \"SingularDisplayName\": \"Logs Metric Filter\" }\n ,\"microsoft.awsconnector/logssubscriptionfilters\": { \"SingularDisplayName\": \"Logs Subscription Filter\" }\n ,\"microsoft.awsconnector/macie2jobsummaries\": { \"SingularDisplayName\": \"Macie2 Job Summary\" }\n ,\"microsoft.awsconnector/macieallowlists\": { \"SingularDisplayName\": \"Macie Allow List\" }\n ,\"microsoft.awsconnector/networkfirewallfirewallpolicies\": { \"SingularDisplayName\": \"Network Firewall Firewall Policy\" }\n ,\"microsoft.awsconnector/networkfirewallfirewalls\": { \"SingularDisplayName\": \"Network Firewall Firewall\" }\n ,\"microsoft.awsconnector/networkfirewallrulegroups\": { \"SingularDisplayName\": \"Network Firewall Rule Group\" }\n ,\"microsoft.awsconnector/opensearchdomainstatuses\": { \"SingularDisplayName\": \"Open Search Domain Status\" }\n ,\"microsoft.awsconnector/opensearchservicedomains\": { \"SingularDisplayName\": \"Open Search Service Domain\" }\n ,\"microsoft.awsconnector/organizationsaccounts\": { \"SingularDisplayName\": \"Organizations Account\" }\n ,\"microsoft.awsconnector/organizationsorganizations\": { \"SingularDisplayName\": \"Organizations Organization\" }\n ,\"microsoft.awsconnector/rdsdbclusters\": { \"SingularDisplayName\": \"RDS DBCluster\" }\n ,\"microsoft.awsconnector/rdsdbinstances\": { \"SingularDisplayName\": \"RDS DBInstance\" }\n ,\"microsoft.awsconnector/rdsdbsnapshotattributesresults\": { \"SingularDisplayName\": \"RDS DBSnapshot Attributes Result\" }\n ,\"microsoft.awsconnector/rdsdbsnapshots\": { \"SingularDisplayName\": \"RDS DBSnapshot\" }\n ,\"microsoft.awsconnector/rdseventsubscriptions\": { \"SingularDisplayName\": \"RDS Event Subscription\" }\n ,\"microsoft.awsconnector/rdsexporttasks\": { \"SingularDisplayName\": \"RDS Export Task\" }\n ,\"microsoft.awsconnector/redshiftclusterparametergroups\": { \"SingularDisplayName\": \"Redshift Cluster Parameter Group\" }\n ,\"microsoft.awsconnector/redshiftclusters\": { \"SingularDisplayName\": \"Redshift Cluster\" }\n ,\"microsoft.awsconnector/route53domainsdomainsummaries\": { \"SingularDisplayName\": \"Route 53 Domains Domain Summary\" }\n ,\"microsoft.awsconnector/route53hostedzones\": { \"SingularDisplayName\": \"Route53 Hosted Zone\" }\n ,\"microsoft.awsconnector/route53resourcerecordsets\": { \"SingularDisplayName\": \"Route 53 Resource Record Set\" }\n ,\"microsoft.awsconnector/s3accesscontrolpolicies\": { \"SingularDisplayName\": \"S3 Access Control Policy\" }\n ,\"microsoft.awsconnector/s3accesspoints\": { \"SingularDisplayName\": \"S3 Access Point\" }\n ,\"microsoft.awsconnector/s3bucketpolicies\": { \"SingularDisplayName\": \"S3 Bucket Policy\" }\n ,\"microsoft.awsconnector/s3buckets\": { \"SingularDisplayName\": \"S3 Bucket\" }\n ,\"microsoft.awsconnector/s3controlmultiregionaccesspointpolicydocuments\": { \"SingularDisplayName\": \"S3 Control Multi Region Access Point Policy Document\" }\n ,\"microsoft.awsconnector/sagemakerapps\": { \"SingularDisplayName\": \"Sage Maker App\" }\n ,\"microsoft.awsconnector/sagemakerdevices\": { \"SingularDisplayName\": \"Sage Maker Device\" }\n ,\"microsoft.awsconnector/sagemakerimages\": { \"SingularDisplayName\": \"Sage Maker Image\" }\n ,\"microsoft.awsconnector/sagemakernotebookinstancesummaries\": { \"SingularDisplayName\": \"Sage Maker Notebook Instance Summary\" }\n ,\"microsoft.awsconnector/secretsmanagerresourcepolicies\": { \"SingularDisplayName\": \"Secrets Manager Resource Policy\" }\n ,\"microsoft.awsconnector/secretsmanagersecrets\": { \"SingularDisplayName\": \"Secrets Manager Secret\" }\n ,\"microsoft.awsconnector/snssubscriptions\": { \"SingularDisplayName\": \"SNS Subscription\" }\n ,\"microsoft.awsconnector/snstopics\": { \"SingularDisplayName\": \"SNS Topic\" }\n ,\"microsoft.awsconnector/sqsqueues\": { \"SingularDisplayName\": \"SQS Queue\" }\n ,\"microsoft.awsconnector/ssminstanceinformations\": { \"SingularDisplayName\": \"SSM Instance Information\" }\n ,\"microsoft.awsconnector/ssmparameters\": { \"SingularDisplayName\": \"SSM Parameter\" }\n ,\"microsoft.awsconnector/ssmresourcecompliancesummaryitems\": { \"SingularDisplayName\": \"SSM Resource Compliance Summary Item\" }\n ,\"microsoft.awsconnector/wafv2ipsets\": { \"SingularDisplayName\": \"WAFv2 IPSet\" }\n ,\"microsoft.awsconnector/wafv2loggingconfigurations\": { \"SingularDisplayName\": \"WAFv2 Logging Configuration\" }\n ,\"microsoft.awsconnector/wafv2webaclassociations\": { \"SingularDisplayName\": \"WAFv2 Web ACLAssociation\" }\n ,\"microsoft.awsconnector/wafwebaclsummaries\": { \"SingularDisplayName\": \"WAF Web ACLSummary\" }\n ,\"microsoft.azureactivedirectory/b2cdirectories\": { \"SingularDisplayName\": \"B2C tenant\" }\n ,\"microsoft.azureactivedirectory/ciamdirectories\": { \"SingularDisplayName\": \"External Configuration Tenant\" }\n ,\"microsoft.azureactivedirectory/guestusages\": { \"SingularDisplayName\": \"Guest Usage\" }\n ,\"microsoft.azurearcdata/datacontrollers\": { \"SingularDisplayName\": \"Azure Arc data controller\" }\n ,\"microsoft.azurearcdata/mysqlserver\": { \"SingularDisplayName\": \"MySql Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/postgresinstances\": { \"SingularDisplayName\": \"PostgreSQL server ? Azure Arc\" }\n ,\"microsoft.azurearcdata/postgressqlserver\": { \"SingularDisplayName\": \"PostgresSql Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlmanagedinstances\": { \"SingularDisplayName\": \"SQL managed instance - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserveresulicenses\": { \"SingularDisplayName\": \"SQL Server ESU license\" }\n ,\"microsoft.azurearcdata/sqlserverinstances\": { \"SingularDisplayName\": \"SQL Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserverinstances/databases\": { \"SingularDisplayName\": \"SQL Server database - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserverlicenses\": { \"SingularDisplayName\": \"SQL Server License\" }\n ,\"microsoft.azurebusinesscontinuity/deletedunifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity deleted unified protected item\" }\n ,\"microsoft.azurebusinesscontinuity/unifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity unified protected item\" }\n ,\"microsoft.azurecis/aadapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis AAD application\" }\n ,\"microsoft.azurecis/addressrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis address record\" }\n ,\"microsoft.azurecis/autopilotenvironments\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot environment\" }\n ,\"microsoft.azurecis/autopilotmachinefunctions\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot machine function\" }\n ,\"microsoft.azurecis/autopilotsoftwareloadbalancevirtualips\": { \"SingularDisplayName\": \"Microsoft.AzureCis auto pilot software load balance virtual IP\" }\n ,\"microsoft.azurecis/azcopies\": { \"SingularDisplayName\": \"Microsoft.AzureCis az copy\" }\n ,\"microsoft.azurecis/canonicalnamerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis canonical name record\" }\n ,\"microsoft.azurecis/dsmsallowlists\": { \"SingularDisplayName\": \"Microsoft.AzureCis ds msallowlist\" }\n ,\"microsoft.azurecis/dsmscertificates\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms certificate\" }\n ,\"microsoft.azurecis/dsmsrootfolders\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms root folder\" }\n ,\"microsoft.azurecis/dstsapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts application\" }\n ,\"microsoft.azurecis/dstsserviceaccounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service account\" }\n ,\"microsoft.azurecis/dstsserviceclientidentities\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service client identity\" }\n ,\"microsoft.azurecis/genericgenevaactions\": { \"SingularDisplayName\": \"Microsoft.AzureCis generic geneva action\" }\n ,\"microsoft.azurecis/plannedquotas\": { \"SingularDisplayName\": \"Microsoft.AzureCis planned quota\" }\n ,\"microsoft.azurecis/pointerrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis pointer record\" }\n ,\"microsoft.azurecis/publishconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis publish config value\" }\n ,\"microsoft.azurecis/pushagentv2accounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis push agent v2 account\" }\n ,\"microsoft.azurecis/servicerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis service record\" }\n ,\"microsoft.azurecis/sharedconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis shared config value\" }\n ,\"microsoft.azurecloudmetadata/clouds\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata cloud\" }\n ,\"microsoft.azurecloudmetadata/clouds/geographies\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geography\" }\n ,\"microsoft.azurecloudmetadata/clouds/geographies/regions\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geographies region\" }\n ,\"microsoft.azuredatatransfer/connections\": { \"SingularDisplayName\": \"Connection\" }\n ,\"microsoft.azuredatatransfer/connections/flows\": { \"SingularDisplayName\": \"Flow\" }\n ,\"microsoft.azuredatatransfer/pipelines\": { \"SingularDisplayName\": \"Pipeline\" }\n ,\"microsoft.azurefleet/fleets\": { \"SingularDisplayName\": \"Compute Fleet\" }\n ,\"microsoft.azurefleet/fleetscomputehub\": { \"SingularDisplayName\": \"Compute Fleet\" }\n ,\"microsoft.azureimagetestingforlinux/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job\" }\n ,\"microsoft.azureimagetestingforlinux/jobtemplates\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job template\" }\n ,\"microsoft.azurelargeinstance/azurelargeinstances\": { \"SingularDisplayName\": \"Azure Large Instance\" }\n ,\"microsoft.azurelargeinstance/azurelargestorageinstances\": { \"SingularDisplayName\": \"Microsoft.AzureLargeInstance Azure large storage instance\" }\n ,\"microsoft.azurepercept/accounts\": { \"SingularDisplayName\": \"Microsoft.AzurePercept account\" }\n ,\"microsoft.azurepercept/accounts/devices\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts device\" }\n ,\"microsoft.azurepercept/accounts/devices/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts devices sensor\" }\n ,\"microsoft.azurepercept/accounts/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts sensor\" }\n ,\"microsoft.azurepercept/accounts/solutioninstances\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solutioninstance\" }\n ,\"microsoft.azurepercept/accounts/solutions\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solution\" }\n ,\"microsoft.azurepercept/accounts/targets\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts target\" }\n ,\"microsoft.azureplaywrightservice/accounts\": { \"SingularDisplayName\": \"Playwright Testing\" }\n ,\"microsoft.azurescan/scanningaccounts\": { \"SingularDisplayName\": \"ESRP Scan\" }\n ,\"microsoft.azuresphere/catalogs\": { \"SingularDisplayName\": \"Azure Sphere Catalog\" }\n ,\"microsoft.azurespherev2/catalogs\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalog\" }\n ,\"microsoft.azurespherev2/catalogs/artifacts\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs artifact\" }\n ,\"microsoft.azurespherev2/catalogs/certificates\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs certificate\" }\n ,\"microsoft.azurespherev2/catalogs/deviceregistrations\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs device registration\" }\n ,\"microsoft.azurespherev2/catalogs/provisioningpackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs provisioning package\" }\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channel\" }\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels/deployments\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channels deployment\" }\n ,\"microsoft.azurespherev2/catalogs/updatepackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs update package\" }\n ,\"microsoft.azurestack/cloudmanifestfiles\": { \"SingularDisplayName\": \"Microsoft.AzureStack cloud manifest file\" }\n ,\"microsoft.azurestack/linkedsubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack linked subscription\" }\n ,\"microsoft.azurestack/registrations\": { \"SingularDisplayName\": \"Microsoft.AzureStack registration\" }\n ,\"microsoft.azurestack/registrations/customersubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations customer subscription\" }\n ,\"microsoft.azurestack/registrations/products\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations product\" }\n ,\"microsoft.azurestackhci/clusters\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/clusters/updates/updateruns\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/clusters/updatesummaries\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/devicepools\": { \"SingularDisplayName\": \"Azure Stack\" }\n ,\"microsoft.azurestackhci/edgedevices\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge device\" }\n ,\"microsoft.azurestackhci/edgedevices/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge devices job\" }\n ,\"microsoft.azurestackhci/edgemachines\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machine\" }\n ,\"microsoft.azurestackhci/edgemachines/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machines job\" }\n ,\"microsoft.azurestackhci/edgenodepools\": { \"SingularDisplayName\": \"Azure Stack\" }\n ,\"microsoft.azurestackhci/galleryimages\": { \"SingularDisplayName\": \"Azure Local Gallery image\" }\n ,\"microsoft.azurestackhci/logicalnetworks\": { \"SingularDisplayName\": \"Azure Local Logical network\" }\n ,\"microsoft.azurestackhci/marketplacegalleryimages\": { \"SingularDisplayName\": \"Azure Local Marketplace Gallery image\" }\n ,\"microsoft.azurestackhci/networkinterfaces\": { \"SingularDisplayName\": \"Azure Local VM Network Interface\" }\n ,\"microsoft.azurestackhci/networksecuritygroups\": { \"SingularDisplayName\": \"Azure Local Network Security Group\" }\n ,\"microsoft.azurestackhci/networksecuritygroups/securityrules\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI network security groups security rule\" }\n ,\"microsoft.azurestackhci/storagecontainers\": { \"SingularDisplayName\": \"Azure Local Storage path\" }\n ,\"microsoft.azurestackhci/virtualharddisks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual hard disk\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instance\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances guest agent\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.azurestackhci/virtualmachines\": { \"SingularDisplayName\": \"Azure Local virtual machine - Azure Arc\" }\n ,\"microsoft.azurestackhci/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual network\" }\n ,\"microsoft.backupsolutions/vmwareapplications\": { \"SingularDisplayName\": \"Microsoft.BackupSolutions vmware application\" }\n ,\"microsoft.bakeryhybrid/pies\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid py\" }\n ,\"microsoft.bakeryhybrid/pies/nestedresourcetype\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid pies nested resource type\" }\n ,\"microsoft.baremetal/baremetalconnections\": { \"SingularDisplayName\": \"Microsoft.BareMetal bare metal connection\" }\n ,\"microsoft.baremetal/crayservers\": { \"SingularDisplayName\": \"Cray Server\" }\n ,\"microsoft.baremetal/monitoringservers\": { \"SingularDisplayName\": \"Monitoring Server\" }\n ,\"microsoft.baremetal/peeringsettings\": { \"SingularDisplayName\": \"Microsoft.BareMetal peering setting\" }\n ,\"microsoft.baremetalinfrastructure/baremetalinstances\": { \"SingularDisplayName\": \"BareMetal Instance\" }\n ,\"microsoft.baremetalinfrastructure/baremetalstorageinstances\": { \"SingularDisplayName\": \"Microsoft.BareMetalInfrastructure bare metal storage instance\" }\n ,\"microsoft.batch/batchaccounts\": { \"SingularDisplayName\": \"Batch account\" }\n ,\"microsoft.billing/billingaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing account\" }\n ,\"microsoft.billing/billingaccounts/agreements\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts agreement\" }\n ,\"microsoft.billing/billingaccounts/associatedtenants\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts associated tenant\" }\n ,\"microsoft.billing/billingaccounts/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts available balance\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profile\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles available balance\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers transfer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/instructions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles instruction\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice section\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections product\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections transfer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/paymentmethodlinks\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles payment method link\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles policy\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/transactions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles transaction\" }\n ,\"microsoft.billing/billingaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptionaliases\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription aliase\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptions/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscriptions invoice\" }\n ,\"microsoft.billing/billingaccounts/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customer\" }\n ,\"microsoft.billing/billingaccounts/customers/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers billing subscription\" }\n ,\"microsoft.billing/billingaccounts/customers/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers policy\" }\n ,\"microsoft.billing/billingaccounts/customers/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers product\" }\n ,\"microsoft.billing/billingaccounts/departments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts department\" }\n ,\"microsoft.billing/billingaccounts/departments/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/departments/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role definition\" }\n ,\"microsoft.billing/billingaccounts/departments/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments enrollment account\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment account\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role definition\" }\n ,\"microsoft.billing/billingaccounts/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\n ,\"microsoft.billing/billingaccounts/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\n ,\"microsoft.billing/billingaccounts/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice\" }\n ,\"microsoft.billing/billingaccounts/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice section\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections billing subscription\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections product\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections transfer\" }\n ,\"microsoft.billing/billingaccounts/lineofcredit\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts line of credit\" }\n ,\"microsoft.billing/billingaccounts/migrations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts migration\" }\n ,\"microsoft.billing/billingaccounts/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts payment method\" }\n ,\"microsoft.billing/billingaccounts/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts policy\" }\n ,\"microsoft.billing/billingaccounts/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts product\" }\n ,\"microsoft.billing/billingaccounts/reservationorders\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation order\" }\n ,\"microsoft.billing/billingaccounts/reservationorders/reservations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation orders reservation\" }\n ,\"microsoft.billing/billingaccounts/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\n ,\"microsoft.billing/billingaccounts/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\n ,\"microsoft.billing/billingperiods\": { \"SingularDisplayName\": \"Microsoft.Billing billing period\" }\n ,\"microsoft.billing/billingproperty\": { \"SingularDisplayName\": \"Microsoft.Billing billing property\" }\n ,\"microsoft.billing/billingrequests\": { \"SingularDisplayName\": \"Microsoft.Billing billing request\" }\n ,\"microsoft.billing/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing role assignment\" }\n ,\"microsoft.billing/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing role definition\" }\n ,\"microsoft.billing/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing enrollment account\" }\n ,\"microsoft.billing/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing payment method\" }\n ,\"microsoft.billing/policies\": { \"SingularDisplayName\": \"Microsoft.Billing policy\" }\n ,\"microsoft.billing/promotions\": { \"SingularDisplayName\": \"Microsoft.Billing promotion\" }\n ,\"microsoft.billing/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing transfer\" }\n ,\"microsoft.billingbenefits/credits\": { \"SingularDisplayName\": \"Credit\" }\n ,\"microsoft.billingbenefits/discounts\": { \"SingularDisplayName\": \"Discount\" }\n ,\"microsoft.billingbenefits/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\n ,\"microsoft.billingbenefits/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\n ,\"microsoft.billingbenefits/maccs\": { \"SingularDisplayName\": \"Microsoft Azure Consumption Commitment\" }\n ,\"microsoft.billingbenefits/reservationorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits reservation order aliase\" }\n ,\"microsoft.billingbenefits/savingsplanorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits savings plan order aliase\" }\n ,\"microsoft.billingbenefits/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\n ,\"microsoft.billingbenefits/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\n ,\"microsoft.bing/accounts\": { \"SingularDisplayName\": \"Bing Resource\" }\n ,\"microsoft.blockchain/blockchainmembers\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain member\" }\n ,\"microsoft.blockchain/blockchainmembers/transactionnodes\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain members transaction node\" }\n ,\"microsoft.blockchaintokens/tokenservices\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token service\" }\n ,\"microsoft.blockchaintokens/tokenservices/blockchainnetworks\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services blockchain network\" }\n ,\"microsoft.blockchaintokens/tokenservices/groups\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services group\" }\n ,\"microsoft.blockchaintokens/tokenservices/groups/accounts\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services groups account\" }\n ,\"microsoft.blockchaintokens/tokenservices/tokentemplates\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services token template\" }\n ,\"microsoft.bluefin/instances\": { \"SingularDisplayName\": \"Microsoft.Bluefin instance\" }\n ,\"microsoft.bluefin/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances dataset\" }\n ,\"microsoft.bluefin/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances pipeline\" }\n ,\"microsoft.blueprint/blueprintassignments\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint assignment\" }\n ,\"microsoft.blueprint/blueprints\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint\" }\n ,\"microsoft.blueprint/blueprints/artifacts\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints artifact\" }\n ,\"microsoft.blueprint/blueprints/versions\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints version\" }\n ,\"microsoft.botservice/botservices\": { \"SingularDisplayName\": \"Bot Service\" }\n ,\"microsoft.cache/redis\": { \"SingularDisplayName\": \"Redis cache\" }\n ,\"microsoft.cache/redisenterprise\": { \"SingularDisplayName\": \"Azure Managed Redis\" }\n ,\"microsoft.cache/redisenterprise/databases\": { \"SingularDisplayName\": \"Redis Enterprise database\" }\n ,\"microsoft.capacity/reservationorders\": { \"SingularDisplayName\": \"Reservation order\" }\n ,\"microsoft.capacity/reservationorders/reservations\": { \"SingularDisplayName\": \"Reservation\" }\n ,\"microsoft.cascade/sites\": { \"SingularDisplayName\": \"Microsoft.Cascade site\" }\n ,\"microsoft.cdn/cdnwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Content Delivery Network WAF policy\" }\n ,\"microsoft.cdn/edgeactions\": { \"SingularDisplayName\": \"Edge Action\" }\n ,\"microsoft.cdn/profiles\": { \"SingularDisplayName\": \"Front Door and CDN profile\" }\n ,\"microsoft.cdn/profiles/afdendpoints\": { \"SingularDisplayName\": \"Endpoint\" }\n ,\"microsoft.cdn/profiles/afdendpoints/routes\": { \"SingularDisplayName\": \"Route\" }\n ,\"microsoft.cdn/profiles/customdomains\": { \"SingularDisplayName\": \"Custom domain\" }\n ,\"microsoft.cdn/profiles/endpoints\": { \"SingularDisplayName\": \"CDN endpoint\" }\n ,\"microsoft.cdn/profiles/endpoints/customdomains\": { \"SingularDisplayName\": \"CDN custom domain\" }\n ,\"microsoft.cdn/profiles/endpoints/origins\": { \"SingularDisplayName\": \"CDN origin\" }\n ,\"microsoft.cdn/profiles/origingroups\": { \"SingularDisplayName\": \"Origin group\" }\n ,\"microsoft.cdn/profiles/origingroups/origins\": { \"SingularDisplayName\": \"Origin\" }\n ,\"microsoft.cdn/profiles/rulesets\": { \"SingularDisplayName\": \"Rule set\" }\n ,\"microsoft.cdn/profiles/rulesets/rules\": { \"SingularDisplayName\": \"Rule\" }\n ,\"microsoft.cdn/profiles/secrets\": { \"SingularDisplayName\": \"Secret\" }\n ,\"microsoft.cdn/profiles/securitypolicies\": { \"SingularDisplayName\": \"Security policy\" }\n ,\"microsoft.certificateregistration/certificateorders\": { \"SingularDisplayName\": \"App Service certificate\" }\n ,\"microsoft.certify/testsuites\": { \"SingularDisplayName\": \"Microsoft.Certify test suite\" }\n ,\"microsoft.certify/validationjobs\": { \"SingularDisplayName\": \"Microsoft.Certify validation job\" }\n ,\"microsoft.changeanalysis/profile\": { \"SingularDisplayName\": \"Microsoft.ChangeAnalysis profile\" }\n ,\"microsoft.changesafety/changestates\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change state\" }\n ,\"microsoft.changesafety/changestates/stageprogressions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change states stage progression\" }\n ,\"microsoft.changesafety/stagemaps\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety stage map\" }\n ,\"microsoft.changesafety/validations\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validation\" }\n ,\"microsoft.changesafety/validators\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validator\" }\n ,\"microsoft.changesafety/validators/versions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validators version\" }\n ,\"microsoft.chaos/experiments\": { \"SingularDisplayName\": \"Chaos Experiment\" }\n ,\"microsoft.chaos/privateaccesses\": { \"SingularDisplayName\": \"Agent Private Access\" }\n ,\"microsoft.chaos/targets\": { \"SingularDisplayName\": \"Microsoft.Chaos target\" }\n ,\"microsoft.chaos/targets/capabilities\": { \"SingularDisplayName\": \"Microsoft.Chaos targets capability\" }\n ,\"microsoft.classiccompute/domainnames\": { \"SingularDisplayName\": \"Cloud service (classic)\" }\n ,\"microsoft.classiccompute/domainnames/slots/roles\": { \"SingularDisplayName\": \"Cloud service role (classic)\" }\n ,\"microsoft.classiccompute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine (classic)\" }\n ,\"microsoft.classicnetwork/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group (classic)\" }\n ,\"microsoft.classicnetwork/reservedips\": { \"SingularDisplayName\": \"Reserved IP address (classic)\" }\n ,\"microsoft.classicnetwork/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network (classic)\" }\n })[tolower(id)]\n}\n", + "$fxv#1": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_2(id: string) {\n dynamic({\n \"microsoft.classicstorage/storageaccounts\": { \"SingularDisplayName\": \"Storage account (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/disks\": { \"SingularDisplayName\": \"Disk (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/osimages\": { \"SingularDisplayName\": \"OS image (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/vmimages\": { \"SingularDisplayName\": \"VM image (classic)\" }\n ,\"microsoft.cleanroom/cleanrooms\": { \"SingularDisplayName\": \"Microsoft.CleanRoom cleanroom\" }\n ,\"microsoft.cleanroom/collaborations\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaboration\" }\n ,\"microsoft.cleanroom/collaborations/contracts\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaborations contract\" }\n ,\"microsoft.cleanroom/consortiums\": { \"SingularDisplayName\": \"Microsoft.CleanRoom consortium\" }\n ,\"microsoft.cleanroom/microservices\": { \"SingularDisplayName\": \"Microsoft.CleanRoom microservice\" }\n ,\"microsoft.cloud/hubs\": { \"SingularDisplayName\": \"FinOps hub\" }\n ,\"microsoft.clouddeviceplatform/delegatedidentities\": { \"SingularDisplayName\": \"Microsoft.CloudDevicePlatform delegated identity\" }\n ,\"microsoft.cloudhealth/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\n ,\"microsoft.cloudtest/accounts\": { \"SingularDisplayName\": \"CloudTest Account\" }\n ,\"microsoft.cloudtest/buildcaches\": { \"SingularDisplayName\": \"1ES Build Cache\" }\n ,\"microsoft.cloudtest/hostedpools\": { \"SingularDisplayName\": \"1ES Hosted Pool\" }\n ,\"microsoft.cloudtest/images\": { \"SingularDisplayName\": \"1ES Image\" }\n ,\"microsoft.cloudtest/pools\": { \"SingularDisplayName\": \"CloudTest Pool\" }\n ,\"microsoft.clusterstor/nodes\": { \"SingularDisplayName\": \"ClusterStor\" }\n ,\"microsoft.codesigning/codesigningaccounts\": { \"SingularDisplayName\": \"Trusted Signing Account\" }\n ,\"microsoft.codespaces/plans\": { \"SingularDisplayName\": \"Microsoft.Codespaces plan\" }\n ,\"microsoft.cognitiveservices/accounts\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.cognitiveservices/accounts/projects\": { \"SingularDisplayName\": \"Azure AI Foundry project\" }\n ,\"microsoft.cognitiveservices/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plan\" }\n ,\"microsoft.cognitiveservices/commitmentplans/accountassociations\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plans account association\" }\n ,\"microsoft.communication/communicationservices\": { \"SingularDisplayName\": \"Communication Service\" }\n ,\"microsoft.communication/emailservices\": { \"SingularDisplayName\": \"Email Communication Service\" }\n ,\"microsoft.communication/emailservices/domains\": { \"SingularDisplayName\": \"Email Communication Services Domain\" }\n ,\"microsoft.community/communitytrainings\": { \"SingularDisplayName\": \"Community Training\" }\n ,\"microsoft.compositesolutions/compositesolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution definition\" }\n ,\"microsoft.compositesolutions/compositesolutions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution\" }\n ,\"microsoft.compute/availabilitysets\": { \"SingularDisplayName\": \"Availability set\" }\n ,\"microsoft.compute/capacityreservationgroups\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\n ,\"microsoft.compute/capacityreservationgroups/capacityreservations\": { \"SingularDisplayName\": \"Capacity reservation\" }\n ,\"microsoft.compute/capacityreservationgroupscomputehub\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\n ,\"microsoft.compute/cloudservices\": { \"SingularDisplayName\": \"Cloud service (extended support)\" }\n ,\"microsoft.compute/computefleetinstances\": { \"SingularDisplayName\": \"Instance\" }\n ,\"microsoft.compute/computefleetscalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.compute/diskaccesses\": { \"SingularDisplayName\": \"Disk Access\" }\n ,\"microsoft.compute/diskencryptionsets\": { \"SingularDisplayName\": \"Disk Encryption Set\" }\n ,\"microsoft.compute/disks\": { \"SingularDisplayName\": \"Disk\" }\n ,\"microsoft.compute/galleries\": { \"SingularDisplayName\": \"Azure compute gallery\" }\n ,\"microsoft.compute/galleries/applications\": { \"SingularDisplayName\": \"VM application definition\" }\n ,\"microsoft.compute/galleries/applications/versions\": { \"SingularDisplayName\": \"VM application version\" }\n ,\"microsoft.compute/galleries/images\": { \"SingularDisplayName\": \"VM image definition\" }\n ,\"microsoft.compute/galleries/images/versions\": { \"SingularDisplayName\": \"VM image version\" }\n ,\"microsoft.compute/galleries/imagescomputehub\": { \"SingularDisplayName\": \"VM image definition\" }\n ,\"microsoft.compute/hostgroups\": { \"SingularDisplayName\": \"Host group\" }\n ,\"microsoft.compute/hostgroups/hosts\": { \"SingularDisplayName\": \"Host\" }\n ,\"microsoft.compute/hostgroupscomputehub\": { \"SingularDisplayName\": \"Host group\" }\n ,\"microsoft.compute/images\": { \"SingularDisplayName\": \"Image\" }\n ,\"microsoft.compute/imagescomputehub\": { \"SingularDisplayName\": \"Image\" }\n ,\"microsoft.compute/locations/communitygalleries/images\": { \"SingularDisplayName\": \"Community image\" }\n ,\"microsoft.compute/locations/communitygalleries/imagescomputehub\": { \"SingularDisplayName\": \"Community image\" }\n ,\"microsoft.compute/proximityplacementgroups\": { \"SingularDisplayName\": \"Proximity placement group\" }\n ,\"microsoft.compute/proximityplacementgroupscomputehub\": { \"SingularDisplayName\": \"Proximity placement group\" }\n ,\"microsoft.compute/restorepointcollections\": { \"SingularDisplayName\": \"Restore Point Collection\" }\n ,\"microsoft.compute/restorepointcollections/restorepoints\": { \"SingularDisplayName\": \"Restore Point\" }\n ,\"microsoft.compute/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\n ,\"microsoft.compute/sshpublickeys\": { \"SingularDisplayName\": \"SSH key\" }\n ,\"microsoft.compute/standbypoolinstance\": { \"SingularDisplayName\": \"Standby pool\" }\n ,\"microsoft.compute/virtualmachinecomputehub\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.compute/virtualmachineflexinstances\": { \"SingularDisplayName\": \"Instance\" }\n ,\"microsoft.compute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.compute/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.compute/virtualmachinescalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.compute/virtualmachinescalesets/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine scale set instance\" }\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines/networkinterfaces/ipconfigurations/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\n ,\"microsoft.compute/virtualmachinescalesetscomputehub\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.computehub/advisorcost\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisoroperationalexcellence\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorperformance\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorreliability\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorsecurity\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/all\": { \"SingularDisplayName\": \"All resources\" }\n ,\"microsoft.computehub/backup\": { \"SingularDisplayName\": \"Backup job\" }\n ,\"microsoft.computehub/computehubmain\": { \"SingularDisplayName\": \"Compute infrastructure\" }\n ,\"microsoft.computehub/healthevents\": { \"SingularDisplayName\": \"Health events\" }\n ,\"microsoft.computehub/linuxostype\": { \"SingularDisplayName\": \"Linux OS\" }\n ,\"microsoft.computehub/microsoftdefenderfreetrialsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\n ,\"microsoft.computehub/microsoftdefenderstandardsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\n ,\"microsoft.computehub/outages\": { \"SingularDisplayName\": \"Outages\" }\n ,\"microsoft.computehub/powerstatedeallocated\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/powerstaterunning\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/powerstatestopped\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/provisioningstatefailedresources\": { \"SingularDisplayName\": \"Provisioning states\" }\n ,\"microsoft.computehub/provisioningstatesucceededresources\": { \"SingularDisplayName\": \"Provisioning states\" }\n ,\"microsoft.computehub/windowsostype\": { \"SingularDisplayName\": \"Windows OS\" }\n ,\"microsoft.computeschedule/autoactions\": { \"SingularDisplayName\": \"Automatic Action\" }\n ,\"microsoft.computeschedule/autoactions/occurrences\": { \"SingularDisplayName\": \"Microsoft.ComputeSchedule auto actions occurrence\" }\n ,\"microsoft.confidentialledger/ledgers\": { \"SingularDisplayName\": \"Confidential Ledger\" }\n ,\"microsoft.confidentialledger/managedccfs\": { \"SingularDisplayName\": \"Managed CCF App\" }\n ,\"microsoft.confluent/agreements\": { \"SingularDisplayName\": \"Microsoft.Confluent agreement\" }\n ,\"microsoft.confluent/organizations\": { \"SingularDisplayName\": \"Confluent organization\" }\n ,\"microsoft.connectedcache/cachenodes\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\n ,\"microsoft.connectedcache/enterprisecustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\n ,\"microsoft.connectedcache/enterprisemcccustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\n ,\"microsoft.connectedcache/enterprisemcccustomers/enterprisemcccachenodes\": { \"SingularDisplayName\": \"MCC CacheNode for Enterprise\" }\n ,\"microsoft.connectedcache/ispcustomers\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\n ,\"microsoft.connectedcredentials/credentials\": { \"SingularDisplayName\": \"Microsoft.ConnectedCredentials credential\" }\n ,\"microsoft.connectedvehicle/platformaccounts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVehicle platform account\" }\n ,\"microsoft.connectedvmwarevsphere/clusters\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere cluster\" }\n ,\"microsoft.connectedvmwarevsphere/datastores\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere datastore\" }\n ,\"microsoft.connectedvmwarevsphere/hosts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere host\" }\n ,\"microsoft.connectedvmwarevsphere/resourcepools\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere resource pool\" }\n ,\"microsoft.connectedvmwarevsphere/vcenters\": { \"SingularDisplayName\": \"VMware vCenter\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instance\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances guest agent\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachines\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine template\" }\n ,\"microsoft.connectedvmwarevsphere/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual network\" }\n ,\"microsoft.consumption/budgets\": { \"SingularDisplayName\": \"Microsoft.Consumption budget\" }\n ,\"microsoft.consumption/credits\": { \"SingularDisplayName\": \"Microsoft.Consumption credit\" }\n ,\"microsoft.consumption/pricesheets\": { \"SingularDisplayName\": \"Microsoft.Consumption pricesheet\" }\n ,\"microsoft.containerinstance/containergroupprofiles\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profile\" }\n ,\"microsoft.containerinstance/containergroupprofiles/revisions\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profiles revision\" }\n ,\"microsoft.containerinstance/containergroups\": { \"SingularDisplayName\": \"Container instances\" }\n ,\"microsoft.containerinstance/ngroups\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance ngroup\" }\n ,\"microsoft.containerregistry/registries\": { \"SingularDisplayName\": \"Container registry\" }\n ,\"microsoft.containerregistry/registries/replications\": { \"SingularDisplayName\": \"Container registry replication\" }\n ,\"microsoft.containerregistry/registries/scopemaps\": { \"SingularDisplayName\": \"Container registry scope map\" }\n ,\"microsoft.containerregistry/registries/tokens\": { \"SingularDisplayName\": \"Container registry token\" }\n ,\"microsoft.containerregistry/registries/webhooks\": { \"SingularDisplayName\": \"Container registry webhook\" }\n ,\"microsoft.containerservice/fleets\": { \"SingularDisplayName\": \"Kubernetes fleet manager\" }\n ,\"microsoft.containerservice/managedclusters\": { \"SingularDisplayName\": \"Kubernetes service\" }\n ,\"microsoft.containerservice/managedclusters/managednamespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes namespace\" }\n ,\"microsoft.containerservice/managedclusters/namespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\n ,\"microsoft.containerservice/managedclustersnapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService managedclustersnapshot\" }\n ,\"microsoft.containerservice/snapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService snapshot\" }\n ,\"microsoft.containerstorage/pools\": { \"SingularDisplayName\": \"Container storage\" }\n ,\"microsoft.costmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.CostManagement alert\" }\n ,\"microsoft.costmanagement/budgets\": { \"SingularDisplayName\": \"Microsoft.CostManagement budget\" }\n ,\"microsoft.costmanagement/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement cloud connector\" }\n ,\"microsoft.costmanagement/connectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement connector\" }\n ,\"microsoft.costmanagement/costallocationrules\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost allocation rule\" }\n ,\"microsoft.costmanagement/costdetailsoperationresults\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost details operation result\" }\n ,\"microsoft.costmanagement/exports\": { \"SingularDisplayName\": \"Microsoft.CostManagement export\" }\n ,\"microsoft.costmanagement/externalbillingaccounts\": { \"SingularDisplayName\": \"Microsoft.CostManagement external billing account\" }\n ,\"microsoft.costmanagement/externalsubscriptions\": { \"SingularDisplayName\": \"Microsoft.CostManagement external subscription\" }\n ,\"microsoft.costmanagement/markuprules\": { \"SingularDisplayName\": \"Microsoft.CostManagement markup rule\" }\n ,\"microsoft.costmanagement/operationstatus\": { \"SingularDisplayName\": \"Microsoft.CostManagement operation statu\" }\n ,\"microsoft.costmanagement/reportconfigs\": { \"SingularDisplayName\": \"Microsoft.CostManagement reportconfig\" }\n ,\"microsoft.costmanagement/reports\": { \"SingularDisplayName\": \"Microsoft.CostManagement report\" }\n ,\"microsoft.costmanagement/scheduledactions\": { \"SingularDisplayName\": \"Microsoft.CostManagement scheduled action\" }\n ,\"microsoft.costmanagement/settings\": { \"SingularDisplayName\": \"Microsoft.CostManagement setting\" }\n ,\"microsoft.costmanagement/views\": { \"SingularDisplayName\": \"Microsoft.CostManagement view\" }\n ,\"microsoft.customerlockbox/requests\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox request\" }\n ,\"microsoft.customerlockbox/tenantoptedin\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox tenant opted in\" }\n ,\"microsoft.customproviders/associations\": { \"SingularDisplayName\": \"Microsoft.CustomProviders association\" }\n ,\"microsoft.customproviders/resourceproviders\": { \"SingularDisplayName\": \"Microsoft.CustomProviders resource provider\" }\n ,\"microsoft.dashboard/dashboards\": { \"SingularDisplayName\": \"Azure Monitor dashboards with Grafana\" }\n ,\"microsoft.dashboard/grafana\": { \"SingularDisplayName\": \"Azure Managed Grafana\" }\n ,\"microsoft.dataaccelerator/indexclusters\": { \"SingularDisplayName\": \"Microsoft.DataAccelerator index cluster\" }\n ,\"microsoft.databasefleetmanager/fleets\": { \"SingularDisplayName\": \"Database fleet manager\" }\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces\": { \"SingularDisplayName\": \"Fleetspaces\" }\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces/databases\": { \"SingularDisplayName\": \"Fleet managed database\" }\n ,\"microsoft.databasefleetmanager/fleets/tiers\": { \"SingularDisplayName\": \"tier\" }\n ,\"microsoft.databasewatcher/watchers\": { \"SingularDisplayName\": \"Database watcher\" }\n ,\"microsoft.databox/jobs\": { \"SingularDisplayName\": \"Azure Data Box\" }\n ,\"microsoft.databoxedge/databoxedgedevices\": { \"SingularDisplayName\": \"Azure Stack Edge / Data Box Gateway\" }\n ,\"microsoft.databricks/accessconnectors\": { \"SingularDisplayName\": \"Access Connector for Azure Databricks\" }\n ,\"microsoft.databricks/workspaces\": { \"SingularDisplayName\": \"Azure Databricks Service\" }\n ,\"microsoft.datacatalog/catalogs\": { \"SingularDisplayName\": \"Data catalog\" }\n ,\"microsoft.datacollaboration/workspaces\": { \"SingularDisplayName\": \"Project CI\" }\n ,\"microsoft.datadog/agreements\": { \"SingularDisplayName\": \"Microsoft.Datadog agreement\" }\n ,\"microsoft.datadog/monitors\": { \"SingularDisplayName\": \"Datadog\" }\n ,\"microsoft.datadog/subscriptionstatuses\": { \"SingularDisplayName\": \"Microsoft.Datadog subscription statuse\" }\n ,\"microsoft.datafactory/datafactories\": { \"SingularDisplayName\": \"Data factory\" }\n ,\"microsoft.datafactory/factories\": { \"SingularDisplayName\": \"Data factory (V2)\" }\n ,\"microsoft.datafactory/factories/pipelines\": { \"SingularDisplayName\": \"Data Factory pipeline\" }\n ,\"microsoft.datafactory/factories/triggers\": { \"SingularDisplayName\": \"Data Factory trigger\" }\n ,\"microsoft.datalakeanalytics/accounts\": { \"SingularDisplayName\": \"Data Lake Analytics account\" }\n ,\"microsoft.datalakestore/accounts\": { \"SingularDisplayName\": \"Data Lake Storage Gen1\" }\n ,\"microsoft.datamigration/databasemigrations\": { \"SingularDisplayName\": \"Microsoft.DataMigration database migration\" }\n ,\"microsoft.datamigration/migrationservices\": { \"SingularDisplayName\": \"Microsoft.DataMigration migration service\" }\n ,\"microsoft.datamigration/services\": { \"SingularDisplayName\": \"Azure Database Migration Service (classic)\" }\n ,\"microsoft.datamigration/services/projects\": { \"SingularDisplayName\": \"Azure Database Migration Project\" }\n ,\"microsoft.datamigration/sqlmigrationservices\": { \"SingularDisplayName\": \"Azure Database Migration Service\" }\n ,\"microsoft.dataprotection/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\n ,\"microsoft.dataprotection/resourceguards\": { \"SingularDisplayName\": \"Resource Guard\" }\n ,\"microsoft.datareplication/replicationfabrics\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabric\" }\n ,\"microsoft.datareplication/replicationfabrics/fabricagents\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agent\" }\n ,\"microsoft.datareplication/replicationfabrics/fabricagents/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agents operation\" }\n ,\"microsoft.datareplication/replicationfabrics/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics operation\" }\n ,\"microsoft.datareplication/replicationvaults\": { \"SingularDisplayName\": \"Data replication vault\" }\n ,\"microsoft.datareplication/replicationvaults/alertsettings\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults alert setting\" }\n ,\"microsoft.datareplication/replicationvaults/events\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults event\" }\n ,\"microsoft.datareplication/replicationvaults/jobs\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults job\" }\n ,\"microsoft.datareplication/replicationvaults/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults jobs operation\" }\n ,\"microsoft.datareplication/replicationvaults/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults operation\" }\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnectionproxies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection proxy\" }\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection\" }\n ,\"microsoft.datareplication/replicationvaults/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private link resource\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected item\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items operation\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems/recoverypoints\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items recovery point\" }\n ,\"microsoft.datareplication/replicationvaults/replicationextensions\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extension\" }\n ,\"microsoft.datareplication/replicationvaults/replicationextensions/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extensions operation\" }\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policy\" }\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policies operation\" }\n ,\"microsoft.datashare/accounts\": { \"SingularDisplayName\": \"Data Share\" }\n ,\"microsoft.dbformariadb/servers\": { \"SingularDisplayName\": \"Azure Database for MariaDB server\" }\n ,\"microsoft.dbformysql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for MySQL flexible server\" }\n ,\"microsoft.dbformysql/servers\": { \"SingularDisplayName\": \"MySQL server\" }\n ,\"microsoft.dbforpostgresql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for PostgreSQL flexible server\" }\n ,\"microsoft.dbforpostgresql/servergroupsv2\": { \"SingularDisplayName\": \"Azure Cosmos DB for PostgreSQL Cluster\" }\n ,\"microsoft.dbforpostgresql/servers\": { \"SingularDisplayName\": \"PostgreSQL server\" }\n ,\"microsoft.delegatednetwork/controller\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork controller\" }\n ,\"microsoft.delegatednetwork/delegatedsubnets\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork delegated subnet\" }\n ,\"microsoft.delegatednetwork/orchestrators\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork orchestrator\" }\n ,\"microsoft.dependencymap/maps\": { \"SingularDisplayName\": \"Microsoft.DependencyMap map\" }\n ,\"microsoft.dependencymap/maps/discoverysources\": { \"SingularDisplayName\": \"Microsoft.DependencyMap maps discovery source\" }\n ,\"microsoft.deploymentmanager/artifactsources\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager artifact source\" }\n ,\"microsoft.deploymentmanager/rollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.deploymentmanager/servicetopologies\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topology\" }\n ,\"microsoft.deploymentmanager/servicetopologies/services\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies service\" }\n ,\"microsoft.deploymentmanager/servicetopologies/services/serviceunits\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies services service unit\" }\n ,\"microsoft.deploymentmanager/steps\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager step\" }\n ,\"microsoft.desktopvirtualization/appattachpackages\": { \"SingularDisplayName\": \"App attach package\" }\n ,\"microsoft.desktopvirtualization/applicationgroups\": { \"SingularDisplayName\": \"Application group\" }\n ,\"microsoft.desktopvirtualization/hostpools\": { \"SingularDisplayName\": \"Host pool\" }\n ,\"microsoft.desktopvirtualization/scalingplans\": { \"SingularDisplayName\": \"Scaling plan\" }\n ,\"microsoft.desktopvirtualization/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.devai/instances\": { \"SingularDisplayName\": \"Microsoft.DevAI instance\" }\n ,\"microsoft.devai/instances/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances experiment\" }\n ,\"microsoft.devai/instances/sandboxes\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandbox\" }\n ,\"microsoft.devai/instances/sandboxes/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandboxes experiment\" }\n ,\"microsoft.devcenter/devcenters\": { \"SingularDisplayName\": \"Dev center\" }\n ,\"microsoft.devcenter/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Dev Box definition\" }\n ,\"microsoft.devcenter/networkconnections\": { \"SingularDisplayName\": \"Network connection\" }\n ,\"microsoft.devcenter/plans\": { \"SingularDisplayName\": \"Dev center plan\" }\n ,\"microsoft.devcenter/projects\": { \"SingularDisplayName\": \"Project\" }\n ,\"microsoft.devcenter/projects/pools\": { \"SingularDisplayName\": \"Pool\" }\n ,\"microsoft.developmentwindows365/developmentcloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.DevelopmentWindows365 development cloud pc delegated msi\" }\n ,\"microsoft.devhub/iacprofiles\": { \"SingularDisplayName\": \"Infrastructure as Code Automation\" }\n ,\"microsoft.devhub/templates\": { \"SingularDisplayName\": \"Microsoft.DevHub template\" }\n ,\"microsoft.devhub/templates/versions\": { \"SingularDisplayName\": \"Microsoft.DevHub templates version\" }\n ,\"microsoft.devhub/workflows\": { \"SingularDisplayName\": \"Microsoft.DevHub workflow\" }\n ,\"microsoft.deviceonboarding/discoveryservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery service\" }\n ,\"microsoft.deviceonboarding/discoveryservices/ownershipvoucherpublickeys\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery services ownership voucher public key\" }\n ,\"microsoft.deviceonboarding/onboardingservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding service\" }\n ,\"microsoft.deviceonboarding/onboardingservices/policies\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding services policy\" }\n ,\"microsoft.deviceregistry/assetendpointprofiles\": { \"SingularDisplayName\": \"IoT Asset Endpoint Profile\" }\n ,\"microsoft.deviceregistry/assets\": { \"SingularDisplayName\": \"IoT Asset\" }\n ,\"microsoft.deviceregistry/billingcontainers\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry billing container\" }\n ,\"microsoft.deviceregistry/devices\": { \"SingularDisplayName\": \"IoT Device\" }\n ,\"microsoft.deviceregistry/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset endpoint profile\" }\n ,\"microsoft.deviceregistry/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset\" }\n ,\"microsoft.deviceregistry/namespaces\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespace\" }\n ,\"microsoft.deviceregistry/namespaces/assetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset endpoint profile\" }\n ,\"microsoft.deviceregistry/namespaces/assets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset\" }\n ,\"microsoft.deviceregistry/namespaces/devices\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces device\" }\n ,\"microsoft.deviceregistry/namespaces/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset endpoint profile\" }\n ,\"microsoft.deviceregistry/namespaces/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset\" }\n ,\"microsoft.deviceregistry/schemaregistries\": { \"SingularDisplayName\": \"IoT Schema Registry\" }\n ,\"microsoft.deviceregistry/schemaregistries/schemas\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schema\" }\n ,\"microsoft.deviceregistry/schemaregistries/schemas/schemaversions\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schemas schema version\" }\n ,\"microsoft.devices/iothubs\": { \"SingularDisplayName\": \"IoT hub\" }\n ,\"microsoft.devices/provisioningservices\": { \"SingularDisplayName\": \"Azure IoT Hub Device Provisioning Service (DPS)\" }\n ,\"microsoft.deviceupdate/accounts\": { \"SingularDisplayName\": \"Device Update for IoT Hub\" }\n ,\"microsoft.deviceupdate/updateaccounts\": { \"SingularDisplayName\": \"Device Update Account\" }\n ,\"microsoft.deviceupdate/updateaccounts/activedeployments\": { \"SingularDisplayName\": \"Device Update Active Deployment\" }\n ,\"microsoft.deviceupdate/updateaccounts/agents\": { \"SingularDisplayName\": \"Device Update Agent\" }\n ,\"microsoft.deviceupdate/updateaccounts/deployments\": { \"SingularDisplayName\": \"Device Update Deployment\" }\n ,\"microsoft.deviceupdate/updateaccounts/deviceclasses\": { \"SingularDisplayName\": \"Device Update Device Class\" }\n ,\"microsoft.deviceupdate/updateaccounts/updates\": { \"SingularDisplayName\": \"Device Update\" }\n ,\"microsoft.devops/pipelines\": { \"SingularDisplayName\": \"Microsoft.DevOps pipeline\" }\n ,\"microsoft.devopsinfrastructure/pools\": { \"SingularDisplayName\": \"Managed DevOps Pool\" }\n ,\"microsoft.devspaces/controllers\": { \"SingularDisplayName\": \"Microsoft.DevSpaces controller\" }\n ,\"microsoft.devtestlab/labs\": { \"SingularDisplayName\": \"DevTest lab\" }\n ,\"microsoft.devtestlab/labs/virtualmachines\": { \"SingularDisplayName\": \"DevTest Lab virtual machine\" }\n ,\"microsoft.devtestlab/schedules\": { \"SingularDisplayName\": \"Microsoft.DevTestLab schedule\" }\n ,\"microsoft.devtunnels/tunnelplans\": { \"SingularDisplayName\": \"Dev Tunnels Domain\" }\n ,\"microsoft.diagnostics/apollo\": { \"SingularDisplayName\": \"Microsoft.Diagnostics apollo\" }\n ,\"microsoft.digitaltwins/digitaltwinsinstances\": { \"SingularDisplayName\": \"Azure Digital Twins\" }\n ,\"microsoft.discovery/agents\": { \"SingularDisplayName\": \"Microsoft Discovery Agent\" }\n ,\"microsoft.discovery/bookshelves\": { \"SingularDisplayName\": \"Microsoft Discovery Bookshelf\" }\n ,\"microsoft.discovery/datacontainers\": { \"SingularDisplayName\": \"Microsoft Discovery Data Container\" }\n ,\"microsoft.discovery/datacontainers/dataassets\": { \"SingularDisplayName\": \"Data asset\" }\n ,\"microsoft.discovery/models\": { \"SingularDisplayName\": \"Microsoft Discovery Model\" }\n ,\"microsoft.discovery/storages\": { \"SingularDisplayName\": \"Microsoft Discovery Storage\" }\n ,\"microsoft.discovery/supercomputers\": { \"SingularDisplayName\": \"Microsoft Discovery Supercomputer\" }\n ,\"microsoft.discovery/supercomputers/nodepools\": { \"SingularDisplayName\": \"Nodepool\" }\n ,\"microsoft.discovery/tools\": { \"SingularDisplayName\": \"Microsoft Discovery Tool\" }\n ,\"microsoft.discovery/workflows\": { \"SingularDisplayName\": \"Microsoft Discovery Workflow\" }\n ,\"microsoft.discovery/workspaces\": { \"SingularDisplayName\": \"Microsoft Discovery Workspace\" }\n ,\"microsoft.discovery/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft Discovery Project\" }\n ,\"microsoft.documentdb/cassandraclusters\": { \"SingularDisplayName\": \"Azure Managed Instance for Apache Cassandra\" }\n ,\"microsoft.documentdb/databaseaccounts\": { \"SingularDisplayName\": \"Cosmos DB account\" }\n ,\"microsoft.documentdb/fleets\": { \"SingularDisplayName\": \"Azure Cosmos DB Fleet\" }\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccounts\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccountswithlocations\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\n ,\"microsoft.documentdb/mongoclusters\": { \"SingularDisplayName\": \"Azure Cosmos DB for MongoDB (vCore)\" }\n ,\"microsoft.documentdb/throughputpools\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pool\" }\n ,\"microsoft.documentdb/throughputpools/throughputpoolaccounts\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pools throughput pool account\" }\n ,\"microsoft.domainregistration/domains\": { \"SingularDisplayName\": \"App Service Domain\" }\n ,\"microsoft.domainregistration/topleveldomains\": { \"SingularDisplayName\": \"Microsoft.DomainRegistration top level domain\" }\n ,\"microsoft.durabletask/namespaces\": { \"SingularDisplayName\": \"Microsoft.DurableTask namespace\" }\n ,\"microsoft.durabletask/namespaces/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\n ,\"microsoft.durabletask/schedulers\": { \"SingularDisplayName\": \"Durable Task Scheduler\" }\n ,\"microsoft.durabletask/schedulers/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\n ,\"microsoft.dynamics365fraudprotection/instances\": { \"SingularDisplayName\": \"Microsoft.Dynamics365FraudProtection instance\" }\n ,\"microsoft.easm/workspaces\": { \"SingularDisplayName\": \"Microsoft Defender EASM\" }\n ,\"microsoft.edge/configurations\": { \"SingularDisplayName\": \"Site configuration\" }\n ,\"microsoft.edge/configurations/arcgatewayconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations arc gateway configuration\" }\n ,\"microsoft.edge/configurations/connectivityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations connectivity configuration\" }\n ,\"microsoft.edge/configurations/dynamicconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configuration\" }\n ,\"microsoft.edge/configurations/dynamicconfigurations/versions\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configurations version\" }\n ,\"microsoft.edge/configurations/networkconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations network configuration\" }\n ,\"microsoft.edge/configurations/securityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations security configuration\" }\n ,\"microsoft.edge/configurations/timeserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations time server configuration\" }\n ,\"microsoft.edge/connectivitystatuses\": { \"SingularDisplayName\": \"Microsoft.Edge connectivity statuse\" }\n ,\"microsoft.edge/disconnectedoperations\": { \"SingularDisplayName\": \"Azure Local - disconnected operations\" }\n ,\"microsoft.edge/siteawareresourcetypes\": { \"SingularDisplayName\": \"Microsoft.Edge site aware resource type\" }\n ,\"microsoft.edge/sites\": { \"SingularDisplayName\": \"Site manager - Azure Arc\" }\n ,\"microsoft.edge/updates\": { \"SingularDisplayName\": \"Microsoft.Edge update\" }\n ,\"microsoft.edgemarketplace/offers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace offer\" }\n ,\"microsoft.edgemarketplace/publishers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace publisher\" }\n ,\"microsoft.edgeorder/addresses\": { \"SingularDisplayName\": \"Azure Edge Hardware Center Address\" }\n ,\"microsoft.edgeorder/bootstrapconfigurations\": { \"SingularDisplayName\": \"Site Key\" }\n ,\"microsoft.edgeorder/orderitems\": { \"SingularDisplayName\": \"Azure Edge Hardware Center\" }\n ,\"microsoft.edgeorder/virtual_orderitems\": { \"SingularDisplayName\": \"Device\" }\n ,\"microsoft.edgezones/extendedzones\": { \"SingularDisplayName\": \"Microsoft.EdgeZones extended zone\" }\n ,\"microsoft.education/grants\": { \"SingularDisplayName\": \"Microsoft.Education grant\" }\n ,\"microsoft.education/labs\": { \"SingularDisplayName\": \"Microsoft.Education lab\" }\n ,\"microsoft.education/labs/joinrequests\": { \"SingularDisplayName\": \"Microsoft.Education labs join request\" }\n ,\"microsoft.education/labs/students\": { \"SingularDisplayName\": \"Microsoft.Education labs student\" }\n ,\"microsoft.education/studentlabs\": { \"SingularDisplayName\": \"Microsoft.Education student lab\" }\n ,\"microsoft.elastic/monitors\": { \"SingularDisplayName\": \"Elastic Cloud Resource\" }\n ,\"microsoft.elasticsan/elasticsans\": { \"SingularDisplayName\": \"Elastic SAN\" }\n ,\"microsoft.energydataplatform/energyservices\": { \"SingularDisplayName\": \"Microsoft.EnergyDataPlatform energy service\" }\n ,\"microsoft.enterpriseknowledgegraph/services\": { \"SingularDisplayName\": \"Microsoft.EnterpriseKnowledgeGraph service\" }\n ,\"microsoft.enterprisesupport/enterprisesupports\": { \"SingularDisplayName\": \"Microsoft.EnterpriseSupport enterprise support\" }\n ,\"microsoft.eventgrid/domains\": { \"SingularDisplayName\": \"Event Grid Domain\" }\n ,\"microsoft.eventgrid/domains/topics\": { \"SingularDisplayName\": \"Event Grid Domain Topic\" }\n ,\"microsoft.eventgrid/eventsubscriptions\": { \"SingularDisplayName\": \"Microsoft.EventGrid event subscription\" }\n ,\"microsoft.eventgrid/extensiontopics\": { \"SingularDisplayName\": \"Event Grid extension topic\" }\n ,\"microsoft.eventgrid/namespaces\": { \"SingularDisplayName\": \"Event Grid Namespace\" }\n ,\"microsoft.eventgrid/namespaces/topics\": { \"SingularDisplayName\": \"Event Grid Namespace Topic\" }\n ,\"microsoft.eventgrid/namespaces/topics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Subscription\" }\n ,\"microsoft.eventgrid/namespaces/topicspaces\": { \"SingularDisplayName\": \"Event Grid Topic Space\" }\n ,\"microsoft.eventgrid/partnerconfigurations\": { \"SingularDisplayName\": \"Event Grid Partner Configuration\" }\n ,\"microsoft.eventgrid/partnerdestinations\": { \"SingularDisplayName\": \"Event Grid Partner Destination\" }\n ,\"microsoft.eventgrid/partnernamespaces\": { \"SingularDisplayName\": \"Event Grid Partner Namespace\" }\n ,\"microsoft.eventgrid/partnernamespaces/channels\": { \"SingularDisplayName\": \"Event Grid Channel\" }\n ,\"microsoft.eventgrid/partnerregistrations\": { \"SingularDisplayName\": \"Event Grid Partner Registration\" }\n ,\"microsoft.eventgrid/partnertopics\": { \"SingularDisplayName\": \"Event Grid Partner Topic\" }\n ,\"microsoft.eventgrid/systemtopics\": { \"SingularDisplayName\": \"Event Grid System Topic\" }\n ,\"microsoft.eventgrid/systemtopics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Grid Subscriptions\" }\n ,\"microsoft.eventgrid/topics\": { \"SingularDisplayName\": \"Event Grid Topic\" }\n ,\"microsoft.eventgrid/topictypes\": { \"SingularDisplayName\": \"Microsoft.EventGrid topic type\" }\n ,\"microsoft.eventgrid/verifiedpartners\": { \"SingularDisplayName\": \"Microsoft.EventGrid verified partner\" }\n ,\"microsoft.eventhub/clusters\": { \"SingularDisplayName\": \"Event Hubs Cluster\" }\n ,\"microsoft.eventhub/namespaces\": { \"SingularDisplayName\": \"Event Hubs namespace\" }\n ,\"microsoft.eventhub/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Event Hubs Geo-DR Alias\" }\n ,\"microsoft.eventhub/namespaces/eventhubs\": { \"SingularDisplayName\": \"Event Hubs Instance\" }\n ,\"microsoft.eventhub/namespaces/providers/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\n ,\"microsoft.eventhub/namespaces/schemagroups\": { \"SingularDisplayName\": \"Schema Group\" }\n ,\"microsoft.experimentation/experimentworkspaces\": { \"SingularDisplayName\": \"Experiment Workspace\" }\n ,\"microsoft.extendedlocation/customlocations\": { \"SingularDisplayName\": \"Custom location\" }\n ,\"microsoft.fabric/capacities\": { \"SingularDisplayName\": \"Fabric Capacity\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/operationresults\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric operation result\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private endpoint connection\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private link resource\" }\n ,\"microsoft.fairfieldgardens/deviceprovisioningstates\": { \"SingularDisplayName\": \"Microsoft.FairfieldGardens device provisioning state\" }\n ,\"microsoft.fairfieldgardens/provisioningresources\": { \"SingularDisplayName\": \"Fairfield Gardens\" }\n ,\"microsoft.fairfieldgardens/provisioningresources/provisioningpolicies\": { \"SingularDisplayName\": \"Provisioning policy\" }\n ,\"microsoft.falcon/namespaces\": { \"SingularDisplayName\": \"Microsoft.Falcon namespace\" }\n ,\"microsoft.features/featureprovidernamespaces/featureconfigurations\": { \"SingularDisplayName\": \"Preview features\" }\n ,\"microsoft.fidalgo/devcenters\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenter\" }\n ,\"microsoft.fidalgo/devcenters/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters attachednetwork\" }\n ,\"microsoft.fidalgo/devcenters/catalogs\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalog\" }\n ,\"microsoft.fidalgo/devcenters/catalogs/items\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalogs item\" }\n ,\"microsoft.fidalgo/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters devboxdefinition\" }\n ,\"microsoft.fidalgo/devcenters/environmenttypes\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters environment type\" }\n ,\"microsoft.fidalgo/devcenters/galleries\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters gallery\" }\n ,\"microsoft.fidalgo/devcenters/galleries/images\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries image\" }\n ,\"microsoft.fidalgo/devcenters/galleries/images/versions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries images version\" }\n ,\"microsoft.fidalgo/devcenters/mappings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters mapping\" }\n ,\"microsoft.fidalgo/machinedefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo machinedefinition\" }\n ,\"microsoft.fidalgo/networksettings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksetting\" }\n ,\"microsoft.fidalgo/networksettings/healthchecks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksettings healthcheck\" }\n ,\"microsoft.fidalgo/projects\": { \"SingularDisplayName\": \"Microsoft.Fidalgo project\" }\n ,\"microsoft.fidalgo/projects/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects attachednetwork\" }\n ,\"microsoft.fidalgo/projects/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects devboxdefinition\" }\n ,\"microsoft.fidalgo/projects/environments\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects environment\" }\n ,\"microsoft.fidalgo/projects/pools\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects pool\" }\n ,\"microsoft.fileshares/fileshares\": { \"SingularDisplayName\": \"File share\" }\n ,\"microsoft.fluidrelay/fluidrelayservers\": { \"SingularDisplayName\": \"Fluid Relay\" }\n ,\"microsoft.footprintmonitoring/profiles\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profile\" }\n ,\"microsoft.footprintmonitoring/profiles/experiments\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles experiment\" }\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoint\" }\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints/conditions\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoints condition\" }\n ,\"microsoft.gallery/myareas/galleryitems\": { \"SingularDisplayName\": \"Template\" }\n ,\"microsoft.genomics/accounts\": { \"SingularDisplayName\": \"Genomics account\" }\n ,\"microsoft.graph/azureadapplication\": { \"SingularDisplayName\": \"Entra application\" }\n ,\"microsoft.graph/azureadapplicationprototype\": { \"SingularDisplayName\": \"Microsoft.Graph Azure ad application prototype\" }\n ,\"microsoft.graphservices/accounts\": { \"SingularDisplayName\": \"Metered API account\" }\n ,\"microsoft.guestconfiguration/guestconfigurationassignments\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignment\" }\n ,\"microsoft.guestconfiguration/guestconfigurationassignments/reports\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignments report\" }\n ,\"microsoft.hanaonazure/hanainstances\": { \"SingularDisplayName\": \"SAP HANA on Azure\" }\n ,\"microsoft.hanaonazure/sapmonitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP Solutions (classic)\" }\n ,\"microsoft.hardware/orders\": { \"SingularDisplayName\": \"Microsoft.Hardware order\" }\n ,\"microsoft.hardwaresecuritymodules/cloudhsmclusters\": { \"SingularDisplayName\": \"Azure Cloud HSM\" }\n ,\"microsoft.hdinsight/clusterpools\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster pool\" }\n ,\"microsoft.hdinsight/clusterpools/clusters\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster\" }\n ,\"microsoft.hdinsight/clusterpools/clusters/instanceviews\": { \"SingularDisplayName\": \"Microsoft.HDInsight clusterpools clusters instance view\" }\n ,\"microsoft.hdinsight/clusters\": { \"SingularDisplayName\": \"HDInsight cluster\" }\n ,\"microsoft.healthbot/healthbots\": { \"SingularDisplayName\": \"Healthcare agent service\" }\n ,\"microsoft.healthcareapis/services\": { \"SingularDisplayName\": \"Azure API for FHIR\" }\n ,\"microsoft.healthcareapis/workspaces\": { \"SingularDisplayName\": \"Health Data Services workspace\" }\n ,\"microsoft.healthcareapis/workspaces/dicomservices\": { \"SingularDisplayName\": \"DICOM service\" }\n ,\"microsoft.healthcareapis/workspaces/fhirservices\": { \"SingularDisplayName\": \"FHIR service\" }\n ,\"microsoft.healthcareapis/workspaces/iotconnectors\": { \"SingularDisplayName\": \"MedTech service\" }\n ,\"microsoft.healthdataaiservices/deidservices\": { \"SingularDisplayName\": \"De-identification Service\" }\n ,\"microsoft.healthmodel/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\n ,\"microsoft.healthplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.HealthPlatform account\" }\n ,\"microsoft.help/diagnostics\": { \"SingularDisplayName\": \"Microsoft.Help diagnostic\" }\n ,\"microsoft.help/selfhelp\": { \"SingularDisplayName\": \"Microsoft.Help self help\" }\n ,\"microsoft.help/simplifiedsolutions\": { \"SingularDisplayName\": \"Microsoft.Help simplified solution\" }\n ,\"microsoft.help/solutions\": { \"SingularDisplayName\": \"Microsoft.Help solution\" }\n ,\"microsoft.help/troubleshooters\": { \"SingularDisplayName\": \"Microsoft.Help troubleshooter\" }\n ,\"microsoft.hpcworkbench/instances\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instance\" }\n ,\"microsoft.hpcworkbench/instances/chambers\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chamber\" }\n ,\"microsoft.hpcworkbench/instances/chambers/accessprofiles\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers access profile\" }\n ,\"microsoft.hpcworkbench/instances/chambers/filerequests\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file request\" }\n ,\"microsoft.hpcworkbench/instances/chambers/files\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file\" }\n ,\"microsoft.hpcworkbench/instances/chambers/storages\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers storage\" }\n ,\"microsoft.hpcworkbench/instances/chambers/workloads\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers workload\" }\n ,\"microsoft.hpcworkbench/instances/consortiums\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances consortium\" }\n ,\"microsoft.hybridcloud/cloudconnections\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connection\" }\n ,\"microsoft.hybridcloud/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connector\" }\n ,\"microsoft.hybridcompute/arcgatewayassociatedresources\": { \"SingularDisplayName\": \"Arc gateway associated resource\" }\n ,\"microsoft.hybridcompute/arcserverwithwac\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/gateways\": { \"SingularDisplayName\": \"Arc gateway\" }\n ,\"microsoft.hybridcompute/licenses\": { \"SingularDisplayName\": \"Extended Security Updates - Windows Server 2012/R2\" }\n ,\"microsoft.hybridcompute/machines\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machines/microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\n ,\"microsoft.hybridcompute/machines/microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\n ,\"microsoft.hybridcompute/machines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.hybridcompute/machinesesu\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinespaygo\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinessoftwareassurance\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinessovereign\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Arc Private Link Scope\" }\n ,\"microsoft.hybridcompute/settings\": { \"SingularDisplayName\": \"Microsoft.HybridCompute setting\" }\n ,\"microsoft.hybridconnectivity/endpoints\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoint\" }\n ,\"microsoft.hybridconnectivity/endpoints/serviceconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoints service configuration\" }\n ,\"microsoft.hybridconnectivity/publiccloudconnectors\": { \"SingularDisplayName\": \"Multicloud connector\" }\n ,\"microsoft.hybridconnectivity/solutionconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configuration\" }\n ,\"microsoft.hybridconnectivity/solutionconfigurations/inventory\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configurations inventory\" }\n ,\"microsoft.hybridconnectivity/solutiontypes\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution type\" }\n ,\"microsoft.hybridcontainerservice/kubernetesversions\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService kubernetes version\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instance\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/agentpools\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances agent pool\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances hybrid identity metadata\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances upgrade profile\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusters\": { \"SingularDisplayName\": \"Kubernetes hybrid - Azure Arc\" }\n ,\"microsoft.hybridcontainerservice/skus\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService SKU\" }\n ,\"microsoft.hybridcontainerservice/storagespaces\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService storage space\" }\n ,\"microsoft.hybridcontainerservice/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService virtual network\" }\n ,\"microsoft.hybriddata/datamanagers\": { \"SingularDisplayName\": \"Microsoft.HybridData data manager\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data service\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definition\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions/jobs\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definitions job\" }\n ,\"microsoft.hybriddata/datamanagers/datastores\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store\" }\n ,\"microsoft.hybriddata/datamanagers/datastoretypes\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store type\" }\n ,\"microsoft.hybriddata/datamanagers/publickeys\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers public key\" }\n ,\"microsoft.hybridnetwork/configurationgroupvalues\": { \"SingularDisplayName\": \"Configuration Group Value\" }\n ,\"microsoft.hybridnetwork/devices\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Device\" }\n ,\"microsoft.hybridnetwork/networkfunctions\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Network Function\" }\n ,\"microsoft.hybridnetwork/proxypublishers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publisher\" }\n ,\"microsoft.hybridnetwork/proxypublishers/artifactstores\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers artifact store\" }\n ,\"microsoft.hybridnetwork/proxypublishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers configuration group schema\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition group\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition groups network function definition version\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design group\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design groups network service design version\" }\n ,\"microsoft.hybridnetwork/publishers\": { \"SingularDisplayName\": \"Publisher\" }\n ,\"microsoft.hybridnetwork/publishers/artifactstores\": { \"SingularDisplayName\": \"Publisher Artifact Store\" }\n ,\"microsoft.hybridnetwork/publishers/artifactstores/artifactmanifests\": { \"SingularDisplayName\": \"Publisher Artifact Manifest\" }\n ,\"microsoft.hybridnetwork/publishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Configuration Group Schema\" }\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Network Function Definition\" }\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Network Function Definition Version\" }\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Network Service Design\" }\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Network Service Design Version\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management container\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rolloutsequences\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout sequence\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rollouttiers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout tier\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specification\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollout\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts/statuses\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollouts statuse\" }\n ,\"microsoft.hybridnetwork/sitenetworkservices\": { \"SingularDisplayName\": \"Site Network Service\" }\n ,\"microsoft.hybridnetwork/sites\": { \"SingularDisplayName\": \"Site\" }\n })[tolower(id)]\n}\n", + "$fxv#10": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Prices |=========================================================================================================\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_transform_v1_2 function\n.create-or-alter function\nwith (docstring='Transforms Prices_raw into FOCUS 1.2.', folder='Prices')\nPrices_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n let prices = materialize(\n Prices_raw\n | extend PricingCurrency = coalesce(Currency, CurrencyCode) // CurrencyCode last as a fallback only\n | extend x_SkuId = coalesce(SkuId, SkuID)\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\n | extend x_SkuTerm = isoMonths(Term)\n | project-rename\n SkuMeter = MeterName,\n x_BaseUnitPrice = BasePrice,\n x_EffectivePeriodEnd = EffectiveEndDate,\n x_EffectivePeriodStart = EffectiveStartDate,\n x_PricingUnitDescription = UnitOfMeasure,\n x_SkuIncludedQuantity = IncludedQuantity,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuMeterType = MeterType,\n x_SkuOfferId = OfferID,\n x_SkuPartNumber = PartNumber,\n x_SkuPriceType = PriceType,\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTier = TierMinimumUnits\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, real(null)) // UnitPrice for savings plan is not the on-demand unit price\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, real(null)) // MarketPrice for savings plan is not the list price\n | extend ChargeCategory = case(\n x_SkuPriceType == 'Consumption', 'Usage',\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\n ''\n )\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\n //\n // Get latest ingested row based on the unique ID\n | extend x_IngestionTime = ingestion_time()\n );\n //\n // Meters for reservations and savings plans to identify commitment eligibility\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\n //\n // Copy list/base/contracted prices from on-demand SKUs\n prices\n | where x_SkuPriceType == 'SavingsPlan'\n // If we use join, specify the shuffle key\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\n //\n // Set CommitmentDiscountCategory for reuse\n | extend CommitmentDiscountCategory = case(\n x_SkuPriceType == 'ReservedInstance', 'Usage',\n x_SkuPriceType == 'SavingsPlan', 'Spend',\n ''\n )\n //\n // Calculate commitment discount eligibility\n // TODO: Would a join be faster?\n // TODO: Check this to ensure it's correct\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\n //\n // TODO: Implement x_CommitmentDiscountNormalizedRatio\n | extend x_CommitmentDiscountNormalizedRatio = real(null)\n //\n // Add PricingUnit and x_PricingBlockSize\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\n | lookup kind=leftouter (PricingUnits) on x_PricingUnitDescription\n //\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, real(null)) // Savings plan prices are for the effective price, not the contracted price\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\n | project\n BillingAccountId = tolower(case(\n BillingProfileId startswith '/', BillingProfileId,\n BillingAccountId startswith '/', BillingAccountId,\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\n )),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType = case(\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\n ''\n ),\n CommitmentDiscountUnit = case(\n isempty(CommitmentDiscountCategory), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), PricingUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', PricingUnit),\n ''\n ),\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory = case(\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed',\n ''\n ),\n PricingCurrency,\n PricingUnit,\n SkuId = coalesce(ProductId, ProductID),\n SkuMeter,\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement = case(\n strlen(x_BillingAccountId) > 32, 'MCA',\n strlen(x_BillingAccountId) < 32, 'EA',\n 'Unknown'\n ),\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingSubcategory = case(\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\n ''\n ),\n x_PricingUnitDescription,\n x_SkuDescription = Product,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\n}\n\n// Prices_final_v1_2 table\n.create-merge table Prices_final_v1_2 (\n BillingAccountId: string,\n BillingAccountName: string,\n BillingCurrency: string,\n ChargeCategory: string,\n CommitmentDiscountCategory: string,\n CommitmentDiscountType: string,\n CommitmentDiscountUnit: string,\n ContractedUnitPrice: real,\n ListUnitPrice: real,\n PricingCategory: string,\n PricingCurrency: string, // Azure\n PricingUnit: string,\n SkuId: string,\n SkuMeter: string, // Azure\n SkuPriceId: string,\n SkuPriceIdv2: string, // Hubs add-on\n x_BaseUnitPrice: real, // Azure\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure MCA\n x_BillingProfileId: string, // Azure MCA\n x_CommitmentDiscountNormalizedRatio: real, // Hubs add-on\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_ContractedUnitPriceDiscount: real, // Hubs add-on\n x_ContractedUnitPriceDiscountPercent: real, // Hubs add-on\n x_EffectivePeriodEnd: datetime, // Azure\n x_EffectivePeriodStart: datetime, // Azure\n x_EffectiveUnitPrice: real, // Azure\n x_EffectiveUnitPriceDiscount: real, // Hubs add-on\n x_EffectiveUnitPriceDiscountPercent: real, // Hubs add-on\n x_IngestionTime: datetime, // Hubs add-on\n x_PricingBlockSize: real, // Hubs add-on\n x_PricingSubcategory: string, // Hubs add-on\n x_PricingUnitDescription: string, // Azure\n x_SkuDescription: string, // Azure\n x_SkuId: string, // Azure\n x_SkuIncludedQuantity: real, // Azure EA\n x_SkuMeterCategory: string, // Azure\n x_SkuMeterId: string, // Azure\n x_SkuMeterSubcategory: string, // Azure\n x_SkuMeterType: string, // Azure\n x_SkuPriceType: string, // Azure\n x_SkuProductId: string, // Azure\n x_SkuRegion: string, // Azure\n x_SkuServiceFamily: string, // Azure\n x_SkuOfferId: string, // Azure EA\n x_SkuPartNumber: string, // Azure EA\n x_SkuTerm: int, // Azure\n x_SkuTier: real, // Azure MCA\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_TotalUnitPriceDiscount: real, // Hubs add-on\n x_TotalUnitPriceDiscountPercent: real // Hubs add-on\n)\n\n// Update policy for Prices_raw -> Prices_final_v1_2\n.alter table Prices_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Prices_raw\",\n \"Query\": \"Prices_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Cost and usage |=================================================================================================\n// Supported versions:\n// - MS: 1.2-preview, 1.0, 1.0-preview(v1)\n// https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0\n// https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024\n// https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 \n// https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All costs transformed to FOCUS 1.2.', folder='Costs')\nCosts_transform_v1_2()\n{\n let checkString = (column: string, oldValue: string, newValue: string) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n let checkInt = (column: string, oldValue: int, newValue: int) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n let checkReal = (column: string, oldValue: real, newValue: real) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n Costs_raw\n //\n // Dedupe rows\n | extend x_IngestionTime = ingestion_time()\n | extend x_ChargeId = ''\n // TODO: Consider adding a unique charge ID per row\n // hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // // 1. Resource hierarchy (including resource name), highest to lowest\n // BillingAccountId,\n // x_InvoiceSectionId,\n // x_AccountOwnerId,\n // SubAccountId,\n // x_ResourceGroupName,\n // ResourceName,\n // // 2. Resource details\n // ResourceId,\n // RegionId,\n // Tags,\n // CommitmentDiscountId,\n // x_CostCenter,\n // // 4. Meter details\n // SkuPriceId,\n // x_SkuMeterId,\n // x_SkuPartNumber,\n // x_SkuOfferId,\n // x_SkuDetails,\n // // 5. Date\n // ChargePeriodStart\n // ))\n //\n // Identify data quality issues\n // TODO: Remove x_SourceChanges in v1_3 (or later)\n | extend x_SourceChanges = trim_end(',', strcat(\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\n 'XEffectiveUnitPriceRoundingError,', ''),\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\n ))\n //\n // Handle provider columns that moved to FOCUS\n | extend PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency)\n //\n // Backup original prices/costs before the merge\n | extend old_ContractedCost = ContractedCost\n | extend old_ContractedUnitPrice = ContractedUnitPrice\n | extend old_ListCost = ListCost\n | extend old_ListUnitPrice = ListUnitPrice\n | extend old_x_EffectiveUnitPrice = x_EffectiveUnitPrice\n //\n // Fix columns needed in other changes\n | extend old_ProviderName = ProviderName, ProviderName = case(\n isnotempty(ProviderName), ProviderName,\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\n ''\n )\n //\n // Identify source\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\n ''\n ))\n // Append version check error code\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\n )\n //\n // Fix quantities\n | extend old_PricingQuantity = PricingQuantity, PricingQuantity = case(\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\n PricingQuantity\n )\n | extend old_ConsumedQuantity = ConsumedQuantity, ConsumedQuantity = case(\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\n ConsumedQuantity\n )\n //\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\n and (isempty(ListUnitPrice) or isempty(ContractedUnitPrice) or ListUnitPrice == 0 or ContractedUnitPrice == 0)\n and x_EffectiveUnitPrice != 0\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\n | as allCosts\n | where tmp_MissingPrices\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | as costsWithMissingPrices\n | join kind=leftouter (\n Prices_final_v1_2\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\n ) on tmp_ReservationPriceLookupKey\n //\n // Select the best price to use for each row\n | extend x_EffectiveUnitPrice = case(\n // If price is a rounding error away from the billed price, use the billed price\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\n // If price is a rounding error away from the contracted price, use the contracted price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\n x_EffectiveUnitPrice\n )\n | extend ContractedUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\n x_EffectiveUnitPrice\n )\n | extend ListUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // Otherwise, assume the contracted price is the same as list price to support aggregations\n ContractedUnitPrice\n )\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\n | extend ContractedCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\n // ContractedCost is 0 in all other scenarios...\n // If 0 and there's a billed cost and prices are the same, use BilledCost\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume EffectiveCost\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\n // Fall back to the original value for any unhandled scenarios\n ContractedCost\n )\n | extend ListCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\n // ListCost is 0 in all other scenarios...\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume ContractedCost\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\n // Fall back to the original value for any unhandled scenarios\n ListCost\n )\n // Merge the rest of the unmodified cost records and remove excess columns\n | union (allCosts | where not(tmp_MissingPrices))\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\n //\n | extend SkuPriceDetails = parse_json(SkuPriceDetails)\n | extend Tags = parse_json(Tags)\n | extend x_SkuDetails = parse_json(x_SkuDetails)\n //\n // Handle FOCUS 1.0-preview\n | extend old_ChargeSubcategory = ChargeSubcategory\n | extend old_ChargeCategory = ChargeCategory, ChargeCategory = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Credit', 'Credit',\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\n ChargeCategory\n )\n | extend old_ChargeClass = ChargeClass, ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass)\n //\n // Populate CapacityReservationId when not specified\n | extend CapacityReservationId = coalesce(CapacityReservationId, tostring(coalesce(x_SkuDetails.VMCapacityReservationId, SkuPriceDetails.VMCapacityReservationId, SkuPriceDetails.x_VMCapacityReservationId)))\n | extend old_CapacityReservationStatus = CapacityReservationStatus, CapacityReservationStatus = case(\n isempty(CapacityReservationId), '',\n isnotempty(CapacityReservationStatus), CapacityReservationStatus,\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\n 'Used'\n )\n //\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\n | extend old_ChargeFrequency = ChargeFrequency, ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency)\n //\n // Commitment discounts\n | extend x_CommitmentDiscountNormalizedRatio = case(\n // Calculate from CommitmentDiscountQuantity, if specified\n isnotempty(CommitmentDiscountQuantity) and CommitmentDiscountQuantity != 0, CommitmentDiscountQuantity / PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\n // Not applicable\n isempty(CommitmentDiscountStatus), real(null),\n // Parse from SKU details if not specified explicitly\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, SkuPriceDetails.RINormalizationRatio, SkuPriceDetails.x_RINormalizationRatio, dynamic(1)))\n )\n | extend old_CommitmentDiscountQuantity = CommitmentDiscountQuantity, CommitmentDiscountQuantity = case(\n // FOCUS 1.2\n isnotempty(CommitmentDiscountQuantity), CommitmentDiscountQuantity,\n // FOCUS 1.0-preview, 1.0\n isempty(CommitmentDiscountStatus), real(null),\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\n real(null)\n )\n | extend old_CommitmentDiscountUnit = CommitmentDiscountUnit, CommitmentDiscountUnit = case(\n // FOCUS 1.2\n isnotempty(CommitmentDiscountUnit), CommitmentDiscountUnit,\n // FOCUS 1.0\n isempty(CommitmentDiscountQuantity), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\n ''\n )\n | extend old_CommitmentDiscountStatus = CommitmentDiscountStatus, CommitmentDiscountStatus = case(\n // FOCUS 1.0+\n isnotempty(CommitmentDiscountStatus), CommitmentDiscountStatus,\n // FOCUS 1.0-preview\n ChargeSubcategory == 'Used Commitment', 'Used',\n ChargeSubcategory == 'Unused Commitment', 'Unused',\n ''\n )\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n //\n // Pricing\n | extend old_x_AmortizationClass = x_AmortizationClass, x_AmortizationClass = case(\n // FOCUS 1.2\n isnotempty(x_AmortizationClass), x_AmortizationClass,\n // FOCUS 1.0-preview+\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\n ''\n )\n | extend old_PricingCategory = PricingCategory, PricingCategory = case(\n // FOCUS 1.0+\n isnotempty(PricingCategory), PricingCategory,\n // FOCUS 1.0-preview\n PricingCategory == 'On-Demand', 'Standard',\n PricingCategory == 'Commitment-Based', 'Committed',\n ''\n )\n //\n // Commitment discount utilization\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n //\n // BUG: Fix ContractedCost that has bad values\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\n //\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), real(null))\n | extend old_ConsumedUnit = ConsumedUnit, ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\n //\n // Convert IDs to lowercase for consistency\n | extend BillingAccountId = tolower(BillingAccountId)\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\n //\n // BUG: Remove EffectiveCost for commitment discount purchases\n | extend old_EffectiveCost = EffectiveCost, EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), EffectiveCost)\n | extend old_x_EffectiveCostInUsd = x_EffectiveCostInUsd, x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), x_EffectiveCostInUsd)\n //\n // Clean up resource columns\n | extend old_ResourceId = ResourceId, ResourceId = case(\n isnotempty(ResourceId), ResourceId,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\n ResourceId\n )\n | extend old_ResourceName = ResourceName, ResourceName = tolower(case(\n isnotempty(ResourceName), ResourceName,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\n ResourceName\n ))\n | extend old_x_ResourceType = x_ResourceType, x_ResourceType = case(\n isnotempty(x_ResourceType), x_ResourceType,\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\n x_ResourceType\n )\n | extend old_ResourceType = ResourceType, ResourceType = case(\n // Use existing resource type display name unless it's an internal resource type ID\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\n // Use CommitmentDiscountType for commitment discount purchases\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\n // Look up display name from internal type\n isnotempty(x_ResourceType), coalesce(tostring(resource_type(x_ResourceType).SingularDisplayName), ResourceType, x_ResourceType),\n ResourceType\n )\n //\n // Handle missing values\n | extend old_PublisherName = PublisherName, PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, '')\n //\n // Handle FOCUS 1.0-preview Region column\n | extend old_Region = Region\n | extend old_RegionId = RegionId, RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region))\n | extend RegionName = coalesce(RegionName, Region)\n //\n // SKU properties\n | extend x_SkuCoreCount = toint(coalesce(SkuPriceDetails.CoreCount, SkuPriceDetails.x_VCPUs, x_SkuDetails.VCPUs, SkuPriceDetails.x_VCores, x_SkuDetails.VCores, SkuPriceDetails.x_vCores, x_SkuDetails.vCores))\n | extend x_SkuInstanceType = tostring(coalesce(SkuPriceDetails.InstanceType, SkuPriceDetails.x_ServiceType, x_SkuDetails.ServiceType, SkuPriceDetails.x_ServerSku, x_SkuDetails.ServerSku))\n | extend x_SkuOperatingSystem = case(\n isnotempty(SkuPriceDetails.OperatingSystem), SkuPriceDetails.OperatingSystem,\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Canonical', 'Linux',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType)\n )\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\n | extend SkuPriceDetails = case(\n // FOCUS 1.2\n isnotempty(SkuPriceDetails), SkuPriceDetails,\n // FOCUS 1.0-preview, 1.0\n parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\n // Prefix all keys with x_ first to avoid double-prefixing\n , @'([\\{,])\"', @'\\1\"x_')\n // CoreCount for number of CPUs/vCPUs/cores/vCores\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\n // TODO: DiskSpace for disk size in GiB\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\n // TODO: GpuCount for the number of GPUs\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\n // TODO: InstanceSeries for the size family/series\n // TODO: MemorySize for the RAM in GiB\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\n // OperatingSystem for the OS name\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\n )\n )\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\n SkuPriceDetails)\n //\n // Azure Hybrid Benefit\n | extend tmp_SqlAhb = tolower(coalesce(x_SkuDetails.AHB, SkuPriceDetails.x_AHB))\n | extend x_SkuLicenseType = case(\n ChargeCategory != 'Usage', '',\n x_SkuMeterCategory in ('Virtual Machines', 'Virtual Machine Licenses') and (x_SkuMeterSubcategory contains 'Windows' or coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL'), 'Windows Server',\n isnotempty(tmp_SqlAhb) or x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\n ''\n )\n | extend x_SkuLicenseStatus = case(\n isempty(x_SkuLicenseType), '',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL' or tmp_SqlAhb == 'true' or x_SkuMeterSubcategory contains 'Azure Hybrid Benefit', 'Enabled',\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not Enabled',\n ''\n )\n | extend x_SkuLicenseQuantity = case(\n isempty(x_SkuCoreCount) or isempty(x_SkuLicenseType), int(null),\n x_SkuCoreCount <= 8, int(8),\n x_SkuCoreCount > 8, x_SkuCoreCount,\n int(null)\n )\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\n //\n // Savings\n | extend x_CommitmentDiscountSavings = iff(isempty(ContractedCost) or ContractedCost == 0 or ContractedCost - EffectiveCost < 0.0001, real(0), ContractedCost - EffectiveCost)\n | extend x_NegotiatedDiscountSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - ContractedCost < 0.0001, real(0), ListCost - ContractedCost)\n | extend x_TotalSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - EffectiveCost < 0.0001, real(0), ListCost - EffectiveCost)\n | extend x_CommitmentDiscountPercent = iff(isempty(ContractedUnitPrice) or ContractedUnitPrice == 0 or ContractedUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\n | extend x_NegotiatedDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - ContractedUnitPrice < 0.0001, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\n | extend x_TotalDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\n //\n // Minor fixes\n | extend old_BillingPeriodEnd = BillingPeriodEnd, BillingPeriodEnd = startofmonth(BillingPeriodEnd)\n | extend old_BillingPeriodStart = BillingPeriodStart, BillingPeriodStart = startofmonth(BillingPeriodStart)\n //\n // Sort columns and apply final transforms\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n CapacityReservationId,\n CapacityReservationStatus,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountQuantity,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\n EffectiveCost,\n InvoiceId = coalesce(InvoiceId, x_InvoiceId),\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory, // TODO: Populate ServiceSubcategory from ServiceName when missing\n SkuId,\n SkuMeter,\n SkuPriceDetails,\n SkuPriceId,\n SubAccountId,\n SubAccountName = iff(isempty(SubAccountId), '', SubAccountName),\n SubAccountType,\n Tags,\n x_AccountId = iff(x_AccountId == '-2', '', x_AccountId),\n x_AccountName = iff(x_AccountId == '-2', '', x_AccountName),\n x_AccountOwnerId = iff(x_AccountId == '-2', '', x_AccountOwnerId),\n x_AmortizationClass,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement = case(\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\n ProviderName\n ),\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingItemCode,\n x_BillingItemName,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountPercent,\n x_CommitmentDiscountSavings,\n x_CommitmentDiscountSpendEligibility = '', // TODO: Add x_CommitmentDiscountSpendEligibility for Costs\n x_CommitmentDiscountUsageEligibility = '', // TODO: Add x_CommitmentDiscountUsageEligibility for Costs\n x_CommitmentDiscountUtilizationAmount,\n x_CommitmentDiscountUtilizationPotential,\n x_CommodityCode,\n x_CommodityName,\n x_ComponentName,\n x_ComponentType,\n x_ConsumedCoreHours,\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd),\n x_CostAllocationRuleName,\n x_CostCategories = parse_json(x_CostCategories),\n x_CostCenter,\n x_CostType,\n x_Credits = parse_json(x_Credits),\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount = parse_json(x_Discount),\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InstanceID,\n x_InvoiceIssuerId,\n x_InvoiceSectionId = case(\n x_InvoiceSectionId == '-2', '',\n x_InvoiceSectionId\n ),\n x_InvoiceSectionName = case(\n x_InvoiceSectionName == 'Unassigned', '',\n x_InvoiceSectionName\n ),\n x_ListCostInUsd,\n x_Location,\n x_NegotiatedDiscountPercent,\n x_NegotiatedDiscountSavings,\n x_Operation,\n x_OwnerAccountID,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription = iff(x_PricingUnitDescription == 'Unassigned', '', x_PricingUnitDescription),\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName = tolower(x_ResourceGroupName),\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServiceModel, // TODO: Populate from ServiceName when missing\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuCoreCount,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuInstanceType,\n x_SkuIsCreditEligible,\n x_SkuLicenseQuantity,\n x_SkuLicenseStatus,\n x_SkuLicenseType,\n x_SkuLicenseUnit,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOperatingSystem,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuPlanName,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceValues = bag_merge(\n checkString('BillingPeriodEnd', old_BillingPeriodEnd, BillingPeriodEnd),\n checkString('BillingPeriodStart', old_BillingPeriodStart, BillingPeriodStart),\n checkString('CapacityReservationStatus', old_CapacityReservationStatus, CapacityReservationStatus),\n checkString('ChargeCategory', old_ChargeCategory, ChargeCategory),\n checkString('ChargeClass', old_ChargeClass, ChargeClass),\n checkString('ChargeSubcategory', old_ChargeSubcategory, ''), // Not included in final schema; use empty string\n checkString('ChargeFrequency', old_ChargeFrequency, ChargeFrequency),\n checkReal('CommitmentDiscountQuantity', old_CommitmentDiscountQuantity, CommitmentDiscountQuantity),\n checkString('CommitmentDiscountUnit', old_CommitmentDiscountUnit, CommitmentDiscountUnit),\n checkString('CommitmentDiscountStatus', old_CommitmentDiscountStatus, CommitmentDiscountStatus),\n checkReal('ConsumedQuantity', old_ConsumedQuantity, ConsumedQuantity),\n checkString('ConsumedUnit', old_ConsumedUnit, ConsumedUnit),\n checkReal('ContractedCost', old_ContractedCost, ContractedCost),\n checkReal('ContractedUnitPrice', old_ContractedUnitPrice, ContractedUnitPrice),\n checkReal('EffectiveCost', old_EffectiveCost, EffectiveCost),\n checkReal('ListCost', old_ListCost, ListCost),\n checkReal('ListUnitPrice', old_ListUnitPrice, ListUnitPrice),\n checkString('PricingCategory', old_PricingCategory, PricingCategory),\n checkReal('PricingQuantity', old_PricingQuantity, PricingQuantity),\n checkString('ProviderName', old_ProviderName, ProviderName),\n checkString('PublisherName', old_PublisherName, PublisherName),\n checkString('Region', old_Region, ''), // Not included in final schema; use empty string\n checkString('RegionId', old_RegionId, RegionId),\n checkString('ResourceId', old_ResourceId, ResourceId),\n checkString('ResourceName', old_ResourceName, ResourceName),\n checkString('ResourceType', old_ResourceType, ResourceType),\n checkString('x_AmortizationClass', old_x_AmortizationClass, x_AmortizationClass),\n checkReal('x_EffectiveCostInUsd', old_x_EffectiveCostInUsd, x_EffectiveCostInUsd),\n checkReal('x_EffectiveUnitPrice', old_x_EffectiveUnitPrice, x_EffectiveUnitPrice),\n checkString('x_ResourceType', old_x_ResourceType, x_ResourceType)\n ),\n x_SourceVersion,\n x_SubproductName,\n x_TotalDiscountPercent,\n x_TotalSavings,\n x_UsageType\n}\n\n// Costs_final_v1_2 table\n.create-merge table Costs_final_v1_2 (\n AvailabilityZone: string,\n BilledCost: real,\n BillingAccountId: string,\n BillingAccountName: string,\n BillingAccountType: string,\n BillingCurrency: string,\n BillingPeriodEnd: datetime,\n BillingPeriodStart: datetime,\n CapacityReservationId: string,\n CapacityReservationStatus: string,\n ChargeCategory: string,\n ChargeClass: string,\n ChargeDescription: string,\n ChargeFrequency: string,\n ChargePeriodEnd: datetime,\n ChargePeriodStart: datetime,\n CommitmentDiscountCategory: string,\n CommitmentDiscountId: string,\n CommitmentDiscountName: string,\n CommitmentDiscountQuantity: real,\n CommitmentDiscountStatus: string,\n CommitmentDiscountType: string,\n CommitmentDiscountUnit: string,\n ConsumedQuantity: real,\n ConsumedUnit: string,\n ContractedCost: real,\n ContractedUnitPrice: real,\n EffectiveCost: real,\n InvoiceId: string,\n InvoiceIssuerName: string,\n ListCost: real,\n ListUnitPrice: real,\n PricingCategory: string,\n PricingCurrency: string,\n PricingQuantity: real,\n PricingUnit: string,\n ProviderName: string,\n PublisherName: string,\n RegionId: string,\n RegionName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n ServiceCategory: string,\n ServiceName: string,\n ServiceSubcategory: string,\n SkuId: string,\n SkuMeter: string,\n SkuPriceDetails: dynamic,\n SkuPriceId: string,\n SubAccountId: string,\n SubAccountName: string,\n SubAccountType: string,\n Tags: dynamic,\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_AmortizationClass: string, // Azure 1.2-preview+\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingItemCode: string, // Alibaba 1.0\n x_BillingItemName: string, // Alibaba 1.0\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_CommitmentDiscountNormalizedRatio: real, // Azure 1.2-preview+\n x_CommitmentDiscountPercent: real, // Hubs add-on\n x_CommitmentDiscountSavings: real, // Hubs add-on\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_CommitmentDiscountUtilizationAmount: real, // Hubs add-on\n x_CommitmentDiscountUtilizationPotential: real, // Hubs add-on\n x_CommodityCode: string, // Alibaba 1.0\n x_CommodityName: string, // Alibaba 1.0\n x_ComponentName: string, // Tencent 1.0\n x_ComponentType: string, // Tencent 1.0\n x_ConsumedCoreHours: real, // Hubs add-on\n x_ContractedCostInUsd: real, // Azure 1.0+\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_CostType: string, // GCP Jan 2024\n x_Credits: dynamic, // GCP Jan 2024\n x_CurrencyConversionRate: real, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: dynamic, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0\n x_IngestionTime: datetime, // Hubs add-on\n x_InstanceID: string, // Alibaba 1.0\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_NegotiatedDiscountPercent:real, // Hubs add-on\n x_NegotiatedDiscountSavings:real, // Hubs add-on\n x_Operation: string, // AWS 1.0\n x_OwnerAccountID: string, // Tencent 1.0\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServiceModel: string, // Azure 1.2-preview+\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuCoreCount: int, // Hubs add-on\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\n x_SkuInstanceType: string, // Hubs add-on\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuLicenseQuantity: int, // Hubs add-on\n x_SkuLicenseStatus: string, // Hubs add-on\n x_SkuLicenseType: string, // Hubs add-on\n x_SkuLicenseUnit: string, // Hubs add-on\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOperatingSystem: string, // Hubs add-on\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuPlanName: string, // Azure 1.2-preview+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceChanges: string, // Hubs add-on\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceValues: dynamic, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubproductName: string, // Tencent 1.0\n x_TotalDiscountPercent: real, // Hubs add-on\n x_TotalSavings: real, // Hubs add-on\n x_UsageType: string // AWS 1.0\n)\n\n// Update policy for Costs_raw -> Costs_final_v1_2 table\n.alter table Costs_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Costs_raw\",\n \"Query\": \"Costs_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Actual costs |===================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_transform_v1_2 function\n.create-or-alter function\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\nActualCosts_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n ActualCosts_raw\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodEnd = Date + 1d,\n ChargePeriodStart = Date,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId = '',\n SkuMeter = MeterName,\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentType = '',\n x_ComponentName = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = '',\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel,\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for ActualCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": true,\n \"Source\": \"ActualCosts_raw\",\n \"Query\": \"ActualCosts_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Amortized costs |================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_transform_v1_2 function\n.create-or-alter function\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\nAmortizedCosts_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n AmortizedCosts_raw\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodEnd = Date + 1d,\n ChargePeriodStart = Date,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId = '',\n SkuMeter = MeterName,\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentType = '',\n x_ComponentName = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = '',\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel,\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for AmortizedCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": true,\n \"Source\": \"AmortizedCosts_raw\",\n \"Query\": \"AmortizedCosts_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All commitment discount usage transformed to FOCUS 1.2. This includes reservationdeatils_raw.', folder='Commitment discounts')\nCommitmentDiscountUsage_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n CommitmentDiscountUsage_raw\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Handle resource columns\n | extend ResourceId = tolower(InstanceId)\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n //\n // Sort columns and apply final transforms\n | project\n ChargePeriodEnd = UsageDate + 1d,\n ChargePeriodStart = UsageDate,\n CommitmentDiscountCategory = 'Usage',\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\n CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\n CommitmentDiscountType = 'Reservation',\n CommitmentDiscountUnit = case(\n InstanceFlexibilityRatio == 1, 'Hours',\n InstanceFlexibilityRatio != 1, 'Normalized Hours',\n ''\n ),\n ConsumedQuantity = UsedHours,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SubAccountId,\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\n x_CommitmentDiscountCommittedAmount = ReservedHours,\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\n x_IngestionTime = ingestion_time(),\n x_ResourceGroupName,\n x_ResourceType,\n // x_RowId = hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // CommitmentDiscountId,\n // ResourceId,\n // ChargePeriodStart\n // )),\n x_ServiceModel,\n x_SkuOrderId = ReservationOrderId,\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\n}\n\n// CommitmentDiscountUsage_final_v1_2 table\n.create-merge table CommitmentDiscountUsage_final_v1_2 (\n ChargePeriodEnd: datetime, // Hubs add-on\n ChargePeriodStart: datetime, // MS 2023-03-01\n CommitmentDiscountCategory: string, // Hubs add-on\n CommitmentDiscountId: string, // MS 2023-03-01\n CommitmentDiscountQuantity: real, // MS 2023-03-01\n CommitmentDiscountType: string, // Hubs add-on\n CommitmentDiscountUnit: string, // Hubs add-on\n ConsumedQuantity: real, // MS 2023-03-01\n ProviderName: string, // Hubs add-on\n ResourceId: string, // MS 2023-03-01\n ResourceName: string, // Hubs add-on\n ResourceType: string, // Hubs add-on\n ServiceCategory: string, // Hubs add-on\n ServiceName: string, // Hubs add-on\n ServiceSubcategory: string, // Hubs add-on\n SubAccountId: string, // Hubs add-on\n x_CommitmentDiscountCommittedCount: real, // MS 2023-03-01\n x_CommitmentDiscountCommittedAmount: real, // MS 2023-03-01\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\n x_CommitmentDiscountNormalizedRatio: real, // MS 2023-03-01\n x_IngestionTime: datetime, // Hubs add-on\n x_ResourceGroupName: string, // Hubs add-on\n x_ResourceType: string, // Hubs add-on\n x_ServiceModel: string, // Hubs add-on\n x_SkuOrderId: string, // MS 2023-03-01\n x_SkuSize: string, // MS 2023-03-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string // Hubs add-on\n)\n\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_2 table\n.alter table CommitmentDiscountUsage_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"CommitmentDiscountUsage_raw\",\n \"Query\": \"CommitmentDiscountUsage_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All recommendations transformed to FOCUS 1.2.', folder='Recommendations')\nRecommendations_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Recommendations_raw\n | extend x_IngestionTime = ingestion_time()\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Convert JSON cost columns to real\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\n //\n // Build recommendation details\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\n | extend x_RecommendationDetails = case(\n // Use incoming x_RecommendationDetails first\n isnotempty(x_RecommendationDetails), x_RecommendationDetails,\n // Create one for reservation recommendations if needed\n x_SourceType == 'ReservationRecommendations', bag_pack(\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\n 'CommitmentDiscountResourceType', ResourceType,\n 'CommitmentDiscountScope', Scope,\n 'LookbackPeriodDuration', case(\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\n ''\n ),\n 'LookbackPeriodStart', FirstUsageDate,\n 'RecommendedQuantity', RecommendedQuantity,\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\n 'RegionId', Location,\n 'RegionName', RegionName,\n 'SkuMeterId', MeterId,\n 'SkuPriceDetails', SkuProperties,\n 'SkuSize', coalesce(SKU, SkuName),\n 'SkuTerm', isoMonths(Term)\n ),\n dynamic({})\n )\n //\n // Prefer specified date, then fall back to generating a date based on reservation recommendation lookback period, then validate to ensure it's not in the future\n | extend x_RecommendationDate = coalesce(x_RecommendationDate, FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d))\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\n //\n | project\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n SubAccountId = coalesce(SubAccountId, iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), '')),\n SubAccountName,\n x_EffectiveCostAfter = coalesce(x_EffectiveCostAfter, TotalCostWithReservedInstances),\n x_EffectiveCostBefore = coalesce(x_EffectiveCostBefore, CostWithNoReservedInstances),\n x_EffectiveCostSavings = coalesce(x_EffectiveCostSavings, NetSavings),\n x_IngestionTime,\n x_RecommendationCategory, // TODO: Set for reservation recommendations\n x_RecommendationDate,\n x_RecommendationDescription,\n x_RecommendationDetails,\n x_RecommendationId, // TODO: Set for reservation recommendations\n x_ResourceGroupName,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n// Recommendations_final_v1_2 table\n.create-merge table Recommendations_final_v1_2 (\n ProviderName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n SubAccountId: string,\n SubAccountName: string,\n x_EffectiveCostAfter: real,\n x_EffectiveCostBefore: real,\n x_EffectiveCostSavings: real,\n x_IngestionTime: datetime,\n x_RecommendationCategory: string,\n x_RecommendationDate: datetime,\n x_RecommendationDescription: string,\n x_RecommendationDetails: dynamic,\n x_RecommendationId: string,\n x_ResourceGroupName: string,\n x_SourceName: string,\n x_SourceProvider: string,\n x_SourceType: string,\n x_SourceVersion: string\n)\n\n// Update policy for Recommendations_raw -> Recommendations_final_v1_2 table\n.alter table Recommendations_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Recommendations_raw\",\n \"Query\": \"Recommendations_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All transactions transformed to FOCUS 1.2.', folder='Transactions')\nTransactions_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Transactions_raw\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Handle BillingPeriodStart/End\n | extend BillingMonth = tostring(BillingMonth)\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\n //\n // Sort columns and apply final transforms\n | project\n BilledCost = Amount,\n BillingAccountId = case(\n BillingProfileId startswith '/', BillingProfileId,\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\n ''\n ),\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\n BillingCurrency = Currency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory = case(\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = case(\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\n EventType == 'Refund', 'Correction',\n ''\n ),\n ChargeDescription = Description,\n ChargeFrequency = case(\n BillingFrequency == 'OneTime', 'One-Time',\n BillingFrequency == 'Recurring', 'Recurring',\n BillingFrequency\n ),\n ChargePeriodStart = EventDate,\n InvoiceId,\n PricingQuantity = Quantity,\n PricingUnit = 'Reservations',\n ProviderName,\n RegionId = Region,\n RegionName = Region,\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerEmail,\n x_CostCenter = CostCenter,\n x_InvoiceNumber = Invoice,\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\n x_IngestionTime = ingestion_time(),\n x_MonetaryCommitment = MonetaryCommitment,\n x_Overage = Overage,\n x_PurchasingBillingAccountId = PurchasingEnrollment,\n x_SkuOrderId = ReservationOrderId,\n x_SkuOrderName = ReservationOrderName,\n x_SkuSize = ArmSkuName,\n x_SkuTerm = isoMonths(Term),\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId = PurchasingSubscriptionGuid,\n x_TransactionType = EventType\n}\n\n// Transactions_final_v1_2 table\n.create-merge table Transactions_final_v1_2 (\n BilledCost: real, // MS CM EA+MCA 2023-05-01\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n ChargeCategory: string, // Hubs add-on\n ChargeClass: string, // Hubs add-on\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n InvoiceId: string, // MS CM MCA 2023-05-01\n PricingQuantity: real, // MS CM EA+MCA 2023-05-01\n PricingUnit: string, // Hubs add-on\n ProviderName: string, // Hubs add-on\n RegionId: string, // MS CM EA+MCA 2023-05-01\n RegionName: string, // MS CM EA+MCA 2023-05-01\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\n x_AccountName: string, // MS CM EA 2023-05-01\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\n x_CostCenter: string, // MS CM EA 2023-05-01\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\n x_IngestionTime: datetime, // Hubs add-on\n x_MonetaryCommitment: real, // MS CM EA 2023-05-01\n x_Overage: real, // MS CM EA 2023-05-01\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\n)\n\n// Update policy for Transactions_raw -> Transactions_final_v1_2 table\n.alter table Transactions_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Transactions_raw\",\n \"Query\": \"Transactions_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n", + "$fxv#11": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Common utility functions\n//\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\n//======================================================================================================================\n\n\n//===| Date functions |=================================================================================================\n\n// monthstring\n.create-or-alter function \nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \nmonthstring(['date']: datetime, length: int = 9)\n{\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\n}\n\n// datestring\n.create-or-alter function \nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n let month = (d: datetime) { monthstring(d, 3) };\n let endDate = iff(end == datetime('0001-01-01'), start, end);\n let sameDate = startofday(start) == startofday(endDate);\n let sameMonth = startofmonth(start) == startofmonth(endDate);\n let sameYear = startofyear(start) == startofyear(endDate);\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\n let currentYear = sameYear and startofyear(start) == startofyear(now());\n case(\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\n fullYear,\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\n // 1 full mo, same year | Mmm yyyy\n fullMonth and sameMonth and sameYear,\n strcat(month(start), ' ', getyear(start)),\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\n fullMonth and sameYear,\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\n fullMonth and not(sameYear),\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\n sameDate,\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\n not(fullMonth) and sameMonth and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\n not(fullMonth) and not(sameMonth) and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\n )\n}\n\n// daterange\n.create-or-alter function \nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n datestring(start, end)\n}\n\n// monthsago\n.create-or-alter function \nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\nmonthsago(months: int)\n{\n datetime_add('month', -months, startofmonth(now()))\n}\n\n\n//===| Number functions |===============================================================================================\n// NOTE: Must be defined before string converters\n\n// delta\n.create-or-alter function \nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \ndelta(oldval: double, newval: double)\n{\n (newval - todouble(oldval))/oldval\n}\n\n// percentOfTotal\n// NOTE: Must be before percent() function\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercentOfTotal(t: (Count: long), tot: long)\n{\n let total = todouble(tot);\n t \n | extend Percent = round(Count / total * 100, 3) \n | order by Count desc\n}\n\n// percent\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercent(t: (Count: long))\n{\n let total = todouble(toscalar(t | summarize sum(Count)));\n percentOfTotal(t, total)\n}\n\n// plusminus\n.create-or-alter function \nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\nplusminus(val: string)\n{\n let neg = substring(val, 0, 1) == '-';\n iff(neg, val, strcat('+', val))\n}\n\n// updown\n.create-or-alter function \nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\nupdown(val: string)\n{\n // TODO: Handle 0\n let neg = substring(val, 0, 1) == '-';\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\n}\n\n\n//===| String functions |===============================================================================================\n\n// percentstring\n// NOTE: Must be defined before deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\npercentstring(num: double, total: double = 1.0, places: int = 9)\n{\n let value = 1.0 * num / total * 100;\n strcat(case(\n places != 9, round(value, places),\n value < 10, round(value, 2),\n round(value, 1)\n ), '%')\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// arraystring\n.create-or-alter function \nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\narraystring(arr: dynamic)\n{\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\n tostring(arr)\n , @'^\\[\"', '')\n , @'\"\\]$', '')\n , @'^, ', '')\n , @', $', '')\n , @'^\\[]$', '')\n , '\",\"', ', ')\n}\n\n// deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\n{\n let d = delta(oldval, newval);\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\n}\n\n// diffstring\n.create-or-alter function \nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\ndiffstring(oldval: double, newval: double, places: int = 1)\n{\n plusminus(round(newval - oldval, places))\n}\n\n// numberstring\n.create-or-alter function \nwith (docstring = 'Convert a number to a string', folder = 'Common')\nnumberstring(num: double, abbrev: bool = true)\n{\n replace_regex(case(\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\n tostring(num)\n ), @'\\.0$', '')\n}\n\n\n//===| Other |==========================================================================================================\n\n// ifempty\n.create-or-alter function \nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\nifempty(val: dynamic, defaultVal: dynamic)\n{\n iff(isempty(val), defaultVal, val)\n}\n", + "$fxv#12": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / Open data functions\n// Wrap Ingestion database tables for easy access.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// PricingUnits\n.create-or-alter function\nwith (docstring = 'Gets pricing units from the FinOps toolkit PricingUnits open data.', folder = 'OpenData')\nPricingUnits()\n{\n database('Ingestion').PricingUnits\n}\n\n// Regions\n.create-or-alter function\nwith (docstring = 'Gets regions from the FinOps toolkit Regions open data.', folder = 'OpenData')\nRegion()\n{\n database('Ingestion').Regions\n}\n\n// ResourceTypes\n.create-or-alter function\nwith (docstring = 'Gets resource types from the FinOps toolkit ResourceTypes open data.', folder = 'OpenData')\nResourceType()\n{\n database('Ingestion').ResourceTypes\n}\n\n// Services\n.create-or-alter function\nwith (docstring = 'Gets services from the FinOps toolkit Services open data.', folder = 'OpenData')\nServices()\n{\n database('Ingestion').Services\n}\n", + "$fxv#13": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / FOCUS 1.0 functions\n// Used for reporting with backward compatibility.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// CommitmentDiscountUsage_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.0.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage_v1_0()\n{\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\n | union (\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\n // Convert real to decimal\n | extend\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n x_CommitmentDiscountCommittedCount = todecimal(x_CommitmentDiscountCommittedCount),\n x_CommitmentDiscountCommittedAmount = todecimal(x_CommitmentDiscountCommittedAmount),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio)\n )\n | project\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountType,\n ConsumedQuantity,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SubAccountId,\n x_CommitmentDiscountCommittedCount,\n x_CommitmentDiscountCommittedAmount,\n x_CommitmentDiscountNormalizedGroup,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountQuantity,\n x_IngestionTime,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceModel,\n x_SkuOrderId,\n x_SkuSize,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Costs_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.0.', folder = 'Costs')\nCosts_v1_0()\n{\n database('Ingestion').Costs_final_v1_0\n | union (\n database('Ingestion').Costs_final_v1_2\n // Convert real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n ContractedCost = todecimal(ContractedCost),\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n EffectiveCost = todecimal(EffectiveCost),\n ListCost = todecimal(ListCost),\n ListUnitPrice = todecimal(ListUnitPrice),\n PricingQuantity = todecimal(PricingQuantity),\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\n // Rename columns\n | project-rename\n x_InvoiceId = InvoiceId,\n x_PricingCurrency = PricingCurrency,\n x_SkuMeterName = SkuMeter\n // Generate historical x_SkuDetails format from SkuPriceDetails\n | extend x_SkuDetails = iff(isnotempty(x_SkuDetails), x_SkuDetails, parse_json(replace_regex(tostring(SkuPriceDetails), @'([\\{,])\"x_', @'\\1\"')))\n )\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost,\n ContractedUnitPrice,\n EffectiveCost,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SkuId,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType,\n Tags,\n x_AccountId,\n x_AccountName,\n x_AccountOwnerId,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_ContractedCostInUsd,\n x_CostAllocationRuleName,\n x_CostCategories,\n x_CostCenter,\n x_Credits,\n x_CostType,\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount,\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InvoiceId,\n x_InvoiceIssuerId,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_ListCostInUsd,\n x_Location,\n x_Operation,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingCurrency,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuIsCreditEligible,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_UsageType\n}\n\n\n// Prices_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all prices aligned to FOCUS 1.0.', folder = 'Prices')\nPrices_v1_0()\n{\n database('Ingestion').Prices_final_v1_0\n | union (\n database('Ingestion').Prices_final_v1_2\n // Convert real to decimal\n | extend\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n ListUnitPrice = todecimal(ListUnitPrice),\n x_BaseUnitPrice = todecimal(x_BaseUnitPrice),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\n x_ContractedUnitPriceDiscount = todecimal(x_ContractedUnitPriceDiscount),\n x_ContractedUnitPriceDiscountPercent = todecimal(x_ContractedUnitPriceDiscountPercent),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_EffectiveUnitPriceDiscount = todecimal(x_EffectiveUnitPriceDiscount),\n x_EffectiveUnitPriceDiscountPercent = todecimal(x_EffectiveUnitPriceDiscountPercent),\n x_PricingBlockSize = todecimal(x_PricingBlockSize),\n x_SkuIncludedQuantity = todecimal(x_SkuIncludedQuantity),\n x_SkuTier = todecimal(x_SkuTier),\n x_TotalUnitPriceDiscount = todecimal(x_TotalUnitPriceDiscount),\n x_TotalUnitPriceDiscountPercent = todecimal(x_TotalUnitPriceDiscountPercent) \n // Rename columns\n | project-rename\n x_PricingCurrency = PricingCurrency,\n x_SkuMeterName = SkuMeter\n )\n | project\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType,\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory,\n PricingUnit,\n SkuId,\n SkuPriceId,\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent,\n x_EffectivePeriodEnd,\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingCurrency,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_SkuDescription,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent\n}\n\n\n// Recommendations_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.0.', folder = 'Recommendations')\nRecommendations_v1_0()\n{\n database('Ingestion').Recommendations_final_v1_0\n | union (\n database('Ingestion').Recommendations_final_v1_2\n // Convert real to decimal\n | extend\n x_EffectiveCostAfter = todecimal(x_EffectiveCostAfter),\n x_EffectiveCostBefore = todecimal(x_EffectiveCostBefore),\n x_EffectiveCostSavings = todecimal(x_EffectiveCostSavings)\n )\n | project\n ProviderName,\n SubAccountId,\n x_IngestionTime,\n x_EffectiveCostAfter,\n x_EffectiveCostBefore,\n x_EffectiveCostSavings,\n x_RecommendationDate,\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Transactions_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.0.', folder = 'Transactions')\nTransactions_v1_0()\n{\n database('Ingestion').Transactions_final_v1_0\n | union (\n database('Ingestion').Transactions_final_v1_2\n // Convert real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n PricingQuantity = todecimal(PricingQuantity),\n x_MonetaryCommitment = todecimal(x_MonetaryCommitment),\n x_Overage = todecimal(x_Overage)\n // Rename columns\n | project-rename\n x_InvoiceId = InvoiceId\n )\n | project\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodStart,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n RegionId,\n RegionName,\n SubAccountId,\n SubAccountName,\n x_AccountName,\n x_AccountOwnerId,\n x_CostCenter,\n x_InvoiceId,\n x_InvoiceNumber,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_IngestionTime,\n x_MonetaryCommitment,\n x_Overage,\n x_PurchasingBillingAccountId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuSize,\n x_SkuTerm,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId,\n x_TransactionType\n}\n", + "$fxv#14": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / FOCUS 1.2 functions\n// Used for reporting with backward compatibility.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// CommitmentDiscountUsage_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.2.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage_v1_2()\n{\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\n | union (\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\n // Convert decimal to real\n | extend\n ConsumedQuantity = toreal(ConsumedQuantity),\n x_CommitmentDiscountCommittedCount = toreal(x_CommitmentDiscountCommittedCount),\n x_CommitmentDiscountCommittedAmount = toreal(x_CommitmentDiscountCommittedAmount),\n x_CommitmentDiscountNormalizedRatio = toreal(x_CommitmentDiscountNormalizedRatio)\n // Add new columns\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceSubcategory) on x_ResourceType\n | extend CommitmentDiscountQuantity = ConsumedQuantity * x_CommitmentDiscountNormalizedRatio\n | extend CommitmentDiscountUnit = case(\n x_CommitmentDiscountNormalizedRatio == 1, 'Hours',\n x_CommitmentDiscountNormalizedRatio > 1, 'Normalized Hours',\n ''\n )\n )\n | project\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountQuantity,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SubAccountId,\n x_CommitmentDiscountCommittedCount,\n x_CommitmentDiscountCommittedAmount,\n x_CommitmentDiscountNormalizedGroup,\n x_CommitmentDiscountNormalizedRatio,\n x_IngestionTime,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceModel,\n x_SkuOrderId,\n x_SkuSize,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Costs_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.2.', folder = 'Costs')\nCosts_v1_2()\n{\n database('Ingestion').Costs_final_v1_2\n | union (\n database('Ingestion').Costs_final_v1_0\n // Convert decimal to real\n | extend\n BilledCost = toreal(BilledCost),\n ConsumedQuantity = toreal(ConsumedQuantity),\n ContractedCost = toreal(ContractedCost),\n ContractedUnitPrice = toreal(ContractedUnitPrice),\n EffectiveCost = toreal(EffectiveCost),\n ListCost = toreal(ListCost),\n ListUnitPrice = toreal(ListUnitPrice),\n PricingQuantity = toreal(PricingQuantity),\n x_BilledCostInUsd = toreal(x_BilledCostInUsd),\n x_BilledUnitPrice = toreal(x_BilledUnitPrice),\n x_BillingExchangeRate = toreal(x_BillingExchangeRate),\n x_ContractedCostInUsd = toreal(x_ContractedCostInUsd),\n x_CurrencyConversionRate = toreal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = toreal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\n x_ListCostInUsd = toreal(x_ListCostInUsd),\n x_PricingBlockSize = toreal(x_PricingBlockSize)\n // Rename columns\n | project-rename\n InvoiceId = x_InvoiceId,\n PricingCurrency = x_PricingCurrency,\n SkuMeter = x_SkuMeterName\n // Add new columns\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | extend CapacityReservationId = tostring(x_SkuDetails.VMCapacityReservationId)\n | extend CapacityReservationStatus = case(\n isempty(CapacityReservationId), '',\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\n 'Used'\n )\n | extend x_CommitmentDiscountNormalizedRatio = case(\n // Not applicable\n isempty(CommitmentDiscountStatus), real(null),\n // Parse from SKU details if not specified explicitly\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, dynamic(1)))\n )\n | extend CommitmentDiscountQuantity = case(\n isempty(CommitmentDiscountStatus), real(null),\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\n real(null)\n )\n | extend CommitmentDiscountUnit = case(\n isempty(CommitmentDiscountQuantity), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\n ''\n )\n | extend x_AmortizationClass = case(\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\n ''\n )\n // Hubs add-ons\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n | extend x_SkuCoreCount = toint(coalesce(x_SkuDetails.VCPUs, x_SkuDetails.VCores, x_SkuDetails.vCores))\n | extend x_SkuInstanceType = tostring(coalesce(x_SkuDetails.ServiceType, x_SkuDetails.ServerSku))\n | extend x_SkuOperatingSystem = case(\n x_SkuDetails.ImageType == 'Canonical', 'Linux',\n x_SkuDetails.ImageType == 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\n x_SkuDetails.ImageType\n )\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\n | extend tmp_SqlAhb = tolower(x_SkuDetails.AHB)\n | extend x_SkuLicenseType = case(\n x_SkuDetails.ImageType contains 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\n ''\n )\n | extend x_SkuLicenseStatus = case(\n isnotempty(x_SkuLicenseType) or tmp_SqlAhb == 'true' or (x_SkuMeterSubcategory contains 'Azure Hybrid Benefit'), 'Enabled',\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not enabled',\n ''\n )\n | extend x_SkuLicenseQuantity = case(\n isempty(x_SkuCoreCount), int(null),\n x_SkuCoreCount <= 8, int(8),\n x_SkuCoreCount > 8, x_SkuCoreCount,\n int(null)\n )\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\n | extend x_CommitmentDiscountSavings = iff(ContractedCost < EffectiveCost, real(0), ContractedCost - EffectiveCost)\n | extend x_NegotiatedDiscountSavings = iff(ListCost < ContractedCost, real(0), ListCost - ContractedCost)\n | extend x_TotalSavings = iff(ListCost < EffectiveCost, real(0), ListCost - EffectiveCost)\n | extend x_CommitmentDiscountPercent = iff(ContractedUnitPrice == 0, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\n | extend x_NegotiatedDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\n | extend x_TotalDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\n // SkuPriceDetails conversion -- Must be after hubs add-ons\n | extend SkuPriceDetails = parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\n // Prefix all keys with x_ first to avoid double-prefixing\n , @'([\\{,])\"', @'\\1\"x_')\n // CoreCount for number of CPUs/vCPUs/cores/vCores\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\n // TODO: DiskSpace for disk size in GiB\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\n // TODO: GpuCount for the number of GPUs\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\n // TODO: InstanceSeries for the size family/series\n // TODO: MemorySize for the RAM in GiB\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\n // OperatingSystem for the OS name\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\n )\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\n SkuPriceDetails)\n )\n | extend SkuPriceDetails = iff(isnotempty(SkuPriceDetails), SkuPriceDetails, parse_json(replace_regex(tostring(x_SkuDetails), @'([\\{,])\"', @'\\1\"x_')))\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n CapacityReservationId,\n CapacityReservationStatus,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountQuantity,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost,\n ContractedUnitPrice,\n EffectiveCost,\n InvoiceId,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId,\n SkuMeter,\n SkuPriceDetails,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType,\n Tags,\n x_AccountId,\n x_AccountName,\n x_AccountOwnerId,\n x_AmortizationClass,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingItemCode,\n x_BillingItemName,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountPercent,\n x_CommitmentDiscountSavings,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_CommitmentDiscountUtilizationAmount,\n x_CommitmentDiscountUtilizationPotential,\n x_CommodityCode,\n x_CommodityName,\n x_ComponentName,\n x_ComponentType,\n x_ConsumedCoreHours,\n x_ContractedCostInUsd,\n x_CostAllocationRuleName,\n x_CostCategories,\n x_CostCenter,\n x_CostType,\n x_Credits,\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount,\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InstanceID,\n x_InvoiceIssuerId,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_ListCostInUsd,\n x_Location,\n x_NegotiatedDiscountPercent,\n x_NegotiatedDiscountSavings,\n x_Operation,\n x_OwnerAccountID,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServiceModel,\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuCoreCount,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuInstanceType,\n x_SkuIsCreditEligible,\n x_SkuLicenseQuantity,\n x_SkuLicenseStatus,\n x_SkuLicenseType,\n x_SkuLicenseUnit,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOperatingSystem,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuPlanName,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceValues,\n x_SourceVersion,\n x_SubproductName,\n x_TotalDiscountPercent,\n x_TotalSavings,\n x_UsageType\n}\n\n\n// Prices_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all prices aligned to FOCUS 1.2.', folder = 'Prices')\nPrices_v1_2()\n{\n database('Ingestion').Prices_final_v1_2\n | union (\n database('Ingestion').Prices_final_v1_0\n // Convert decimal to real\n | extend\n ContractedUnitPrice = toreal(ContractedUnitPrice),\n ListUnitPrice = toreal(ListUnitPrice),\n x_BaseUnitPrice = toreal(x_BaseUnitPrice),\n x_ContractedUnitPriceDiscount = toreal(x_ContractedUnitPriceDiscount),\n x_ContractedUnitPriceDiscountPercent = toreal(x_ContractedUnitPriceDiscountPercent),\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\n x_EffectiveUnitPriceDiscount = toreal(x_EffectiveUnitPriceDiscount),\n x_EffectiveUnitPriceDiscountPercent = toreal(x_EffectiveUnitPriceDiscountPercent),\n x_PricingBlockSize = toreal(x_PricingBlockSize),\n x_SkuIncludedQuantity = toreal(x_SkuIncludedQuantity),\n x_SkuTier = toreal(x_SkuTier),\n x_TotalUnitPriceDiscount = toreal(x_TotalUnitPriceDiscount),\n x_TotalUnitPriceDiscountPercent = toreal(x_TotalUnitPriceDiscountPercent) \n // Rename columns\n | project-rename\n PricingCurrency = x_PricingCurrency,\n SkuMeter = x_SkuMeterName\n )\n | project\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingUnit,\n SkuId,\n SkuMeter,\n SkuPriceId,\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent,\n x_EffectivePeriodEnd,\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_SkuDescription,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent\n}\n\n\n// Recommendations_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.2.', folder = 'Recommendations')\nRecommendations_v1_2()\n{\n database('Ingestion').Recommendations_final_v1_2\n | union (\n database('Ingestion').Recommendations_final_v1_0\n // Convert decimal to real\n | extend\n x_EffectiveCostAfter = toreal(x_EffectiveCostAfter),\n x_EffectiveCostBefore = toreal(x_EffectiveCostBefore),\n x_EffectiveCostSavings = toreal(x_EffectiveCostSavings)\n )\n | project\n ProviderName,\n SubAccountId,\n x_IngestionTime,\n x_EffectiveCostAfter,\n x_EffectiveCostBefore,\n x_EffectiveCostSavings,\n x_RecommendationDate,\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Transactions_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.2.', folder = 'Transactions')\nTransactions_v1_2()\n{\n database('Ingestion').Transactions_final_v1_2\n | union (\n database('Ingestion').Transactions_final_v1_0\n // Convert decimal to real\n | extend\n BilledCost = toreal(BilledCost),\n PricingQuantity = toreal(PricingQuantity),\n x_MonetaryCommitment = toreal(x_MonetaryCommitment),\n x_Overage = toreal(x_Overage)\n // Rename columns\n | project-rename\n InvoiceId = x_InvoiceId\n )\n | project\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodStart,\n InvoiceId,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n RegionId,\n RegionName,\n SubAccountId,\n SubAccountName,\n x_AccountName,\n x_AccountOwnerId,\n x_CostCenter,\n x_InvoiceNumber,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_IngestionTime,\n x_MonetaryCommitment,\n x_Overage,\n x_PurchasingBillingAccountId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuSize,\n x_SkuTerm,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId,\n x_TransactionType\n}\n\n\n//======================================================================================================================\n// Latest FOCUS version\n//======================================================================================================================\n\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage()\n{\n CommitmentDiscountUsage_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\nCosts()\n{\n Costs_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\nPrices()\n{\n Prices_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\nRecommendations()\n{\n Recommendations_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\nTransactions()\n{\n Transactions_v1_2()\n}\n", + "$fxv#15": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / Latest FOCUS version functions\n// Used for ad hoc queries.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage()\n{\n CommitmentDiscountUsage_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\nCosts()\n{\n Costs_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\nPrices()\n{\n Prices_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\nRecommendations()\n{\n Recommendations_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\nTransactions()\n{\n Transactions_v1_2()\n}\n", + "$fxv#2": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_3(id: string) {\n dynamic({\n \"microsoft.hybridnetwork/vendors\": { \"SingularDisplayName\": \"Azure Network Function Manager ? vendor\" }\n ,\"microsoft.hybridonboarding/extensionmanagers\": { \"SingularDisplayName\": \"Microsoft.HybridOnboarding extension manager\" }\n ,\"microsoft.impact/connectors\": { \"SingularDisplayName\": \"Impact Reporting Connector\" }\n ,\"microsoft.impact/impactcategories\": { \"SingularDisplayName\": \"Microsoft.Impact impact category\" }\n ,\"microsoft.impact/topologyimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact topology impact\" }\n ,\"microsoft.impact/workloadimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact workload impact\" }\n ,\"microsoft.impact/workloadimpacts/insights\": { \"SingularDisplayName\": \"Microsoft.Impact workload impacts insight\" }\n ,\"microsoft.importexport/jobs\": { \"SingularDisplayName\": \"Microsoft.ImportExport job\" }\n ,\"microsoft.insights/actiongroups\": { \"SingularDisplayName\": \"Action group\" }\n ,\"microsoft.insights/activitylogalerts\": { \"SingularDisplayName\": \"Activity log alert rule\" }\n ,\"microsoft.insights/alertrules\": { \"SingularDisplayName\": \"Microsoft.Insights alertrule\" }\n ,\"microsoft.insights/alertrules/incidents\": { \"SingularDisplayName\": \"Microsoft.insights alertrules incident\" }\n ,\"microsoft.insights/autoscalesettings\": { \"SingularDisplayName\": \"Microsoft.Insights autoscalesetting\" }\n ,\"microsoft.insights/components\": { \"SingularDisplayName\": \"Application Insights app\" }\n ,\"microsoft.insights/datacollectionendpoints\": { \"SingularDisplayName\": \"Data collection endpoint\" }\n ,\"microsoft.insights/datacollectionruleassociations\": { \"SingularDisplayName\": \"Microsoft.Insights data collection rule association\" }\n ,\"microsoft.insights/datacollectionrules\": { \"SingularDisplayName\": \"Data collection rule\" }\n ,\"microsoft.insights/datacollectionrulesresources\": { \"SingularDisplayName\": \"Data collection rule associated resource\" }\n ,\"microsoft.insights/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\n ,\"microsoft.insights/diagnosticsettingscategories\": { \"SingularDisplayName\": \"Microsoft.Insights diagnostic settings category\" }\n ,\"microsoft.insights/guestdiagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic setting\" }\n ,\"microsoft.insights/guestdiagnosticsettingsassociation\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic settings association\" }\n ,\"microsoft.insights/logprofiles\": { \"SingularDisplayName\": \"Microsoft.Insights logprofile\" }\n ,\"microsoft.insights/metricalerts\": { \"SingularDisplayName\": \"Metric alert rule\" }\n ,\"microsoft.insights/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights notification statu\" }\n ,\"microsoft.insights/privatelinkscopeoperationstatuses\": { \"SingularDisplayName\": \"Microsoft.insights private link scope operation statuse\" }\n ,\"microsoft.insights/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Monitor Private Link Scope\" }\n ,\"microsoft.insights/scheduledqueryrules\": { \"SingularDisplayName\": \"Log search alert rule\" }\n ,\"microsoft.insights/tenantactiongroups\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action group\" }\n ,\"microsoft.insights/tenantactiongroups/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action groups notification statu\" }\n ,\"microsoft.insights/vminsightsonboardingstatuses\": { \"SingularDisplayName\": \"Microsoft.Insights VM insights onboarding statuse\" }\n ,\"microsoft.insights/webtests\": { \"SingularDisplayName\": \"Application Insights availability test\" }\n ,\"microsoft.insights/workbooks\": { \"SingularDisplayName\": \"Azure Workbook\" }\n ,\"microsoft.insights/workbooktemplates\": { \"SingularDisplayName\": \"Azure Workbook Template\" }\n ,\"microsoft.integrationspaces/spaces\": { \"SingularDisplayName\": \"Integration Environment\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twin\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/assets\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins asset\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/executionplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins execution plan\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/testplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test plan\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/tests\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test\" }\n ,\"microsoft.inventory/subscriptioninternalproperties\": { \"SingularDisplayName\": \"Microsoft.Inventory subscription internal property\" }\n ,\"microsoft.iotcentral/iotapps\": { \"SingularDisplayName\": \"IoT Central Application\" }\n ,\"microsoft.iotfirmwaredefense/workspaces\": { \"SingularDisplayName\": \"Firmware analysis workspace\" }\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmware\" }\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares/summaries\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmwares summary\" }\n ,\"microsoft.iotoperations/instances\": { \"SingularDisplayName\": \"Azure IoT Operations\" }\n ,\"microsoft.iotoperations/instances/brokers\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances broker\" }\n ,\"microsoft.iotoperations/instances/brokers/authentications\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authentication\" }\n ,\"microsoft.iotoperations/instances/brokers/authorizations\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authorization\" }\n ,\"microsoft.iotoperations/instances/brokers/listeners\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers listener\" }\n ,\"microsoft.iotoperations/instances/dataflowendpoints\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow endpoint\" }\n ,\"microsoft.iotoperations/instances/dataflowprofiles\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profile\" }\n ,\"microsoft.iotoperations/instances/dataflowprofiles/dataflows\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profiles dataflow\" }\n ,\"microsoft.iotoperationsdataprocessor/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instance\" }\n ,\"microsoft.iotoperationsdataprocessor/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances dataset\" }\n ,\"microsoft.iotoperationsdataprocessor/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances pipeline\" }\n ,\"microsoft.iotoperationsmq/mq\": { \"SingularDisplayName\": \"IoT Operations Ops MQ\" }\n ,\"microsoft.iotoperationsmq/mq/broker\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker\" }\n ,\"microsoft.iotoperationsmq/mq/broker/authentication\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authentication\" }\n ,\"microsoft.iotoperationsmq/mq/broker/authorization\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authorization\" }\n ,\"microsoft.iotoperationsmq/mq/broker/listener\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker listener\" }\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector\" }\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector topic map\" }\n ,\"microsoft.iotoperationsmq/mq/diagnosticservice\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq diagnostic service\" }\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector\" }\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector topic map\" }\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector\" }\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector topic map\" }\n ,\"microsoft.iotoperationsorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator instance\" }\n ,\"microsoft.iotoperationsorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator solution\" }\n ,\"microsoft.iotoperationsorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator target\" }\n ,\"microsoft.iotsecurity/alerttypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity alert type\" }\n ,\"microsoft.iotsecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity defender setting\" }\n ,\"microsoft.iotsecurity/onpremisesensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity on premise sensor\" }\n ,\"microsoft.iotsecurity/recommendationtypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity recommendation type\" }\n ,\"microsoft.iotsecurity/sensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity sensor\" }\n ,\"microsoft.iotsecurity/sites\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity site\" }\n ,\"microsoft.keyvault/managedhsms\": { \"SingularDisplayName\": \"Azure Key Vault Managed HSM\" }\n ,\"microsoft.keyvault/vaults\": { \"SingularDisplayName\": \"Key vault\" }\n ,\"microsoft.kubernetes/connectedclusters\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc extension\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc namespace\" }\n ,\"microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\n ,\"microsoft.kubernetesconfiguration/extensiontypes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension type\" }\n ,\"microsoft.kubernetesconfiguration/extensiontypes/versions\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension types version\" }\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configuration\" }\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations/operations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configurations operation\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scope\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private endpoint connection\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private link resource\" }\n ,\"microsoft.kubernetesconfiguration/sourcecontrolconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration source control configuration\" }\n ,\"microsoft.kubernetesruntime/bgppeers\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime bgp peer\" }\n ,\"microsoft.kubernetesruntime/loadbalancers\": { \"SingularDisplayName\": \"Arc Load Balancer\" }\n ,\"microsoft.kubernetesruntime/services\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime service\" }\n ,\"microsoft.kubernetesruntime/storageclasses\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime storage class\" }\n ,\"microsoft.kusto/clusters\": { \"SingularDisplayName\": \"Azure Data Explorer Cluster\" }\n ,\"microsoft.kusto/clusters/databases\": { \"SingularDisplayName\": \"Azure Data Explorer Database\" }\n ,\"microsoft.labservices/labaccounts\": { \"SingularDisplayName\": \"Lab account\" }\n ,\"microsoft.labservices/labaccounts/labs\": { \"SingularDisplayName\": \"Lab\" }\n ,\"microsoft.labservices/labplans\": { \"SingularDisplayName\": \"Lab plan\" }\n ,\"microsoft.labservices/labs\": { \"SingularDisplayName\": \"Lab\" }\n ,\"microsoft.liftrpilot/organizations\": { \"SingularDisplayName\": \"Azure Pilot\" }\n ,\"microsoft.loadtestservice/loadtestmappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test mapping\" }\n ,\"microsoft.loadtestservice/loadtestprofilemappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test profile mapping\" }\n ,\"microsoft.loadtestservice/loadtests\": { \"SingularDisplayName\": \"Azure Load Testing\" }\n ,\"microsoft.loadtestservice/playwrightworkspaces\": { \"SingularDisplayName\": \"Playwright Workspace\" }\n ,\"microsoft.logic/businessprocesses\": { \"SingularDisplayName\": \"Business Process\" }\n ,\"microsoft.logic/integrationaccounts\": { \"SingularDisplayName\": \"Logic app integration account\" }\n ,\"microsoft.logic/integrationserviceenvironments\": { \"SingularDisplayName\": \"Integration Service Environment\" }\n ,\"microsoft.logic/integrationserviceenvironments/health\": { \"SingularDisplayName\": \"Microsoft.Logic integration service environments health\" }\n ,\"microsoft.logic/integrationserviceenvironments/managedapis\": { \"SingularDisplayName\": \"Managed Connector\" }\n ,\"microsoft.logic/templates\": { \"SingularDisplayName\": \"Logic Apps Template\" }\n ,\"microsoft.logic/workflows\": { \"SingularDisplayName\": \"Logic app\" }\n ,\"microsoft.logz/monitors\": { \"SingularDisplayName\": \"Logz.io\" }\n ,\"microsoft.logz/monitors/accounts\": { \"SingularDisplayName\": \"Logz sub account\" }\n ,\"microsoft.m365/m365resources\": { \"SingularDisplayName\": \"Microsoft.M365 m365 resource\" }\n ,\"microsoft.m365consumptionservices/services\": { \"SingularDisplayName\": \"Microsoft.M365ConsumptionServices service\" }\n ,\"microsoft.machinelearning/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plan\" }\n ,\"microsoft.machinelearning/commitmentplans/commitmentassociations\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plans commitment association\" }\n ,\"microsoft.machinelearning/webservices\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) web service\" }\n ,\"microsoft.machinelearning/workspaces\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) workspace\" }\n ,\"microsoft.machinelearningexperimentation/accounts\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation account\" }\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspace\" }\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspaces project\" }\n ,\"microsoft.machinelearningservices/aistudio\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.machinelearningservices/aistudiocreate\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.machinelearningservices/registries\": { \"SingularDisplayName\": \"Azure Machine Learning registry\" }\n ,\"microsoft.machinelearningservices/workspaces\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints\": { \"SingularDisplayName\": \"Machine learning online endpoint\" }\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints/deployments\": { \"SingularDisplayName\": \"Machine learning online deployment\" }\n ,\"microsoft.machinelearningservices/workspacescreate\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\n ,\"microsoft.maintenance/configurationassignments\": { \"SingularDisplayName\": \"Microsoft.Maintenance configuration assignment\" }\n ,\"microsoft.maintenance/maintenanceconfigurations\": { \"SingularDisplayName\": \"Maintenance Configuration\" }\n ,\"microsoft.maintenance/maintenanceconfigurationsaumbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\n ,\"microsoft.maintenance/maintenanceconfigurationsbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\n ,\"microsoft.maintenance/publicmaintenanceconfigurations\": { \"SingularDisplayName\": \"Microsoft.Maintenance public maintenance configuration\" }\n ,\"microsoft.managedidentity/identities\": { \"SingularDisplayName\": \"Microsoft.ManagedIdentity identity\" }\n ,\"microsoft.managedidentity/userassignedidentities\": { \"SingularDisplayName\": \"Managed Identity\" }\n ,\"microsoft.managednetwork/managednetworks\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed network\" }\n ,\"microsoft.managednetwork/managednetworks/managednetworkgroups\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network group\" }\n ,\"microsoft.managednetwork/managednetworks/managednetworkpeeringpolicies\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network peering policy\" }\n ,\"microsoft.managednetworkfabric/accesscontrollists\": { \"SingularDisplayName\": \"Access Control List (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/internetgatewayrules\": { \"SingularDisplayName\": \"Internet Gateway Rule (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/internetgateways\": { \"SingularDisplayName\": \"Internet Gateway (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipcommunities\": { \"SingularDisplayName\": \"IP Community (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipextendedcommunities\": { \"SingularDisplayName\": \"IP Extended Community (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipprefixes\": { \"SingularDisplayName\": \"IP Prefix (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l2isolationdomains\": { \"SingularDisplayName\": \"Layer 2 Isolation Domain (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains\": { \"SingularDisplayName\": \"Layer 3 Isolation Domain (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains/externalnetworks\": { \"SingularDisplayName\": \"External Network (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains/internalnetworks\": { \"SingularDisplayName\": \"Internal Network (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/neighborgroups\": { \"SingularDisplayName\": \"Neighbor Group (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkdevices\": { \"SingularDisplayName\": \"Network Device (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkdevices/networkinterfaces\": { \"SingularDisplayName\": \"Network Interface (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabriccontrollers\": { \"SingularDisplayName\": \"Network Fabric Controller (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabrics\": { \"SingularDisplayName\": \"Network Fabric (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabrics/networktonetworkinterconnects\": { \"SingularDisplayName\": \"Network to Network Interconnect (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabricskus\": { \"SingularDisplayName\": \"Network Fabric SKU (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkmonitors\": { \"SingularDisplayName\": \"Microsoft.ManagedNetworkFabric network monitor\" }\n ,\"microsoft.managednetworkfabric/networkpacketbrokers\": { \"SingularDisplayName\": \"Network Packet Broker (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkracks\": { \"SingularDisplayName\": \"Network Rack (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networktaprules\": { \"SingularDisplayName\": \"Network Tap Rule (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networktaps\": { \"SingularDisplayName\": \"Network Tap (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/routepolicies\": { \"SingularDisplayName\": \"Route Policy (Operator Nexus)\" }\n ,\"microsoft.managedservices/marketplaceregistrationdefinitions\": { \"SingularDisplayName\": \"Microsoft.ManagedServices marketplace registration definition\" }\n ,\"microsoft.managedservices/registrationassignments\": { \"SingularDisplayName\": \"Microsoft.ManagedServices registration assignment\" }\n ,\"microsoft.managedservices/registrationdefinitions\": { \"SingularDisplayName\": \"Azure Lighthouse\" }\n ,\"microsoft.management/managementgroups\": { \"SingularDisplayName\": \"Microsoft.Management management group\" }\n ,\"microsoft.management/managementgroups/microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\n ,\"microsoft.management/managementgroups/providers/privatelinkassociations\": { \"SingularDisplayName\": \"Application Gateway\" }\n ,\"microsoft.management/managementgroups/providers/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\n ,\"microsoft.management/managementgroups/settings\": { \"SingularDisplayName\": \"Microsoft.Management management groups setting\" }\n ,\"microsoft.management/managementgroups/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Management management groups subscription\" }\n ,\"microsoft.management/servicegroups\": { \"SingularDisplayName\": \"Service group\" }\n ,\"microsoft.managementpartner/partners\": { \"SingularDisplayName\": \"Microsoft.ManagementPartner partner\" }\n ,\"microsoft.manufacturingplatform/manufacturingdataservices\": { \"SingularDisplayName\": \"Factory Operations Agent in Azure AI Foundry\" }\n ,\"microsoft.maps/accounts\": { \"SingularDisplayName\": \"Azure Maps Account\" }\n ,\"microsoft.maps/accounts/creators\": { \"SingularDisplayName\": \"Azure Maps Creator Resource\" }\n ,\"microsoft.marketplace/privatestores\": { \"SingularDisplayName\": \"Microsoft.Marketplace private store\" }\n ,\"microsoft.marketplace/privatestores/adminrequestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores admin request approval\" }\n ,\"microsoft.marketplace/privatestores/collections\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collection\" }\n ,\"microsoft.marketplace/privatestores/collections/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collections offer\" }\n ,\"microsoft.marketplace/privatestores/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores offer\" }\n ,\"microsoft.marketplace/privatestores/requestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores request approval\" }\n ,\"microsoft.media/mediaservices\": { \"SingularDisplayName\": \"Media service\" }\n ,\"microsoft.media/mediaservices/accountfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services account filter\" }\n ,\"microsoft.media/mediaservices/assets\": { \"SingularDisplayName\": \"Microsoft.Media media services asset\" }\n ,\"microsoft.media/mediaservices/assets/assetfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services assets asset filter\" }\n ,\"microsoft.media/mediaservices/assets/tracks\": { \"SingularDisplayName\": \"Microsoft.Media media services assets track\" }\n ,\"microsoft.media/mediaservices/assets/tracks/operationresults\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation result\" }\n ,\"microsoft.media/mediaservices/assets/tracks/operationstatuses\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation statuse\" }\n ,\"microsoft.media/mediaservices/contentkeypolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services content key policy\" }\n ,\"microsoft.media/mediaservices/liveevents\": { \"SingularDisplayName\": \"Live event\" }\n ,\"microsoft.media/mediaservices/liveevents/liveoutputs\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices live events live output\" }\n ,\"microsoft.media/mediaservices/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private endpoint connection\" }\n ,\"microsoft.media/mediaservices/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private link resource\" }\n ,\"microsoft.media/mediaservices/streamingendpoints\": { \"SingularDisplayName\": \"Streaming Endpoint\" }\n ,\"microsoft.media/mediaservices/streaminglocators\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming locator\" }\n ,\"microsoft.media/mediaservices/streamingpolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming policy\" }\n ,\"microsoft.media/mediaservices/transforms\": { \"SingularDisplayName\": \"Microsoft.Media media services transform\" }\n ,\"microsoft.media/mediaservices/transforms/jobs\": { \"SingularDisplayName\": \"Microsoft.Media media services transforms job\" }\n ,\"microsoft.mesh/worlds\": { \"SingularDisplayName\": \"Microsoft.Mesh world\" }\n ,\"microsoft.mesh/worlds/events\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds event\" }\n ,\"microsoft.mesh/worlds/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds events access policy\" }\n ,\"microsoft.mesh/worlds/spaces\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds space\" }\n ,\"microsoft.mesh/worlds/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds spaces access policy\" }\n ,\"microsoft.mesh/worlds/templates\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds template\" }\n ,\"microsoft.mesh/worlds/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds templates access policy\" }\n ,\"microsoft.messagingcatalog/catalogs\": { \"SingularDisplayName\": \"Microsoft.MessagingCatalog catalog\" }\n ,\"microsoft.messagingconnectors/connectors\": { \"SingularDisplayName\": \"Microsoft.MessagingConnectors connector\" }\n ,\"microsoft.metaverse/metaverses\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverse\" }\n ,\"microsoft.metaverse/metaverses/events\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses event\" }\n ,\"microsoft.metaverse/metaverses/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses events access policy\" }\n ,\"microsoft.metaverse/metaverses/spaces\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses space\" }\n ,\"microsoft.metaverse/metaverses/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses spaces access policy\" }\n ,\"microsoft.metaverse/metaverses/templates\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses template\" }\n ,\"microsoft.metaverse/metaverses/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses templates access policy\" }\n ,\"microsoft.migrate/assessmentprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment project\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/clusters\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments cluster\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/assessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment\" }\n ,\"microsoft.migrate/assessmentprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/assessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments avs assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business case\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/avssummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases avs summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedavsmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated avs machine\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated machine\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedsqlentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated sql entity\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated web app\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/iaassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases iaas summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/overviewsummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases overview summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/paassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases paas summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects group\" }\n ,\"microsoft.migrate/assessmentprojects/groups/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessments assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessments avs assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql database\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql instance\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/recommendedassessedentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments recommended assessed entity\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments web app service plan\" }\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessment\" }\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/hypervcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects hypervcollector\" }\n ,\"microsoft.migrate/assessmentprojects/importcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects importcollector\" }\n ,\"microsoft.migrate/assessmentprojects/importsqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects import sql collector\" }\n ,\"microsoft.migrate/assessmentprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects machine\" }\n ,\"microsoft.migrate/assessmentprojects/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private endpoint connection\" }\n ,\"microsoft.migrate/assessmentprojects/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private link resource\" }\n ,\"microsoft.migrate/assessmentprojects/projectsummary\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects project summary\" }\n ,\"microsoft.migrate/assessmentprojects/servercollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects servercollector\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql database\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql instance\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql machine\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/sqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sqlcollector\" }\n ,\"microsoft.migrate/assessmentprojects/vmwarecollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects vmwarecollector\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments web app service plan\" }\n ,\"microsoft.migrate/assessmentprojects/webappcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app collector\" }\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessment\" }\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessments summary\" }\n ,\"microsoft.migrate/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate project\" }\n ,\"microsoft.migrate/migrateprojects/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database instance\" }\n ,\"microsoft.migrate/migrateprojects/databases\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database\" }\n ,\"microsoft.migrate/migrateprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects machine\" }\n ,\"microsoft.migrate/migrateprojects/migrateevents\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects migrate event\" }\n ,\"microsoft.migrate/migrateprojects/solutions\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects solution\" }\n ,\"microsoft.migrate/modernizeprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize project\" }\n ,\"microsoft.migrate/modernizeprojects/deployedresources\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects deployed resource\" }\n ,\"microsoft.migrate/modernizeprojects/jobs\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects job\" }\n ,\"microsoft.migrate/modernizeprojects/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects jobs operation\" }\n ,\"microsoft.migrate/modernizeprojects/migrateagents\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agent\" }\n ,\"microsoft.migrate/modernizeprojects/migrateagents/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agents operation\" }\n ,\"microsoft.migrate/modernizeprojects/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects operation\" }\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployment\" }\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployments operation\" }\n ,\"microsoft.migrate/modernizeprojects/workloadinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instance\" }\n ,\"microsoft.migrate/modernizeprojects/workloadinstances/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instances operation\" }\n ,\"microsoft.migrate/movecollections\": { \"SingularDisplayName\": \"Microsoft.Migrate move collection\" }\n ,\"microsoft.migrate/movecollections/moveresources\": { \"SingularDisplayName\": \"Microsoft.Migrate move collections move resource\" }\n ,\"microsoft.migrate/projects\": { \"SingularDisplayName\": \"Migration project\" }\n ,\"microsoft.mission/approvals\": { \"SingularDisplayName\": \"Approval\" }\n ,\"microsoft.mission/catalogs\": { \"SingularDisplayName\": \"Catalog\" }\n ,\"microsoft.mission/communities\": { \"SingularDisplayName\": \"Community\" }\n ,\"microsoft.mission/communities/communityendpoints\": { \"SingularDisplayName\": \"Community endpoint\" }\n ,\"microsoft.mission/communities/transithubs\": { \"SingularDisplayName\": \"Transit hub\" }\n ,\"microsoft.mission/enclaveconnections\": { \"SingularDisplayName\": \"Enclave connection\" }\n ,\"microsoft.mission/externalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission external connection\" }\n ,\"microsoft.mission/internalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission internal connection\" }\n ,\"microsoft.mission/virtualenclaves\": { \"SingularDisplayName\": \"Enclave\" }\n ,\"microsoft.mission/virtualenclaves/enclaveendpoints\": { \"SingularDisplayName\": \"Enclave endpoint\" }\n ,\"microsoft.mission/virtualenclaves/endpoints\": { \"SingularDisplayName\": \"Endpoint\" }\n ,\"microsoft.mission/virtualenclaves/workloads\": { \"SingularDisplayName\": \"Workload\" }\n ,\"microsoft.mixedreality/objectanchorsaccounts\": { \"SingularDisplayName\": \"Object Anchors Account\" }\n ,\"microsoft.mixedreality/objectunderstandingaccounts\": { \"SingularDisplayName\": \"Object Understanding Account\" }\n ,\"microsoft.mixedreality/remoterenderingaccounts\": { \"SingularDisplayName\": \"Remote Rendering Account\" }\n ,\"microsoft.mixedreality/spatialanchorsaccounts\": { \"SingularDisplayName\": \"Spatial Anchors Account\" }\n ,\"microsoft.mixedreality/spatialmapsaccounts\": { \"SingularDisplayName\": \"Microsoft.MixedReality spatial maps account\" }\n ,\"microsoft.mobilenetwork/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork amf deployment\" }\n ,\"microsoft.mobilenetwork/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork cluster service\" }\n ,\"microsoft.mobilenetwork/mobilenetworks\": { \"SingularDisplayName\": \"Mobile Network\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/datanetworks\": { \"SingularDisplayName\": \"Data Network\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/services\": { \"SingularDisplayName\": \"Service\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/simpolicies\": { \"SingularDisplayName\": \"SIM Policy\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/sites\": { \"SingularDisplayName\": \"Mobile Network Site\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/slices\": { \"SingularDisplayName\": \"Slice\" }\n ,\"microsoft.mobilenetwork/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nrf deployment\" }\n ,\"microsoft.mobilenetwork/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nssf deployment\" }\n ,\"microsoft.mobilenetwork/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork observability service\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes\": { \"SingularDisplayName\": \"Packet Core Control Plane\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes\": { \"SingularDisplayName\": \"Packet Core Data Plane\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes/attacheddatanetworks\": { \"SingularDisplayName\": \"Attached Data Network\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplaneversions\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork packet core control plane version\" }\n ,\"microsoft.mobilenetwork/radioaccessnetworks\": { \"SingularDisplayName\": \"Radio Access Network Insights\" }\n ,\"microsoft.mobilenetwork/sdmdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sdm deployment\" }\n ,\"microsoft.mobilenetwork/simgroups\": { \"SingularDisplayName\": \"SIM Group\" }\n ,\"microsoft.mobilenetwork/simgroups/sims\": { \"SingularDisplayName\": \"SIM\" }\n ,\"microsoft.mobilenetwork/sims\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sim\" }\n ,\"microsoft.mobilenetwork/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork smf deployment\" }\n ,\"microsoft.mobilenetwork/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork upf deployment\" }\n ,\"microsoft.mobilenetwork/virtualizedmmedeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork virtualized mme deployment\" }\n ,\"microsoft.mobilenetwork/vnfagentdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork vnf agent deployment\" }\n ,\"microsoft.mobilepacketcore/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore amf deployment\" }\n ,\"microsoft.mobilepacketcore/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore cluster service\" }\n ,\"microsoft.mobilepacketcore/networkfunctions\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore network function\" }\n ,\"microsoft.mobilepacketcore/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nrf deployment\" }\n ,\"microsoft.mobilepacketcore/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nssf deployment\" }\n ,\"microsoft.mobilepacketcore/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore observability service\" }\n ,\"microsoft.mobilepacketcore/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore smf deployment\" }\n ,\"microsoft.mobilepacketcore/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore upf deployment\" }\n ,\"microsoft.modsimworkbench/workbenches\": { \"SingularDisplayName\": \"Modeling and Simulation Workbench\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers\": { \"SingularDisplayName\": \"Chamber\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/connectors\": { \"SingularDisplayName\": \"Chamber Connector\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/filerequests\": { \"SingularDisplayName\": \"Chamber Data Pipeline File Request\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/files\": { \"SingularDisplayName\": \"Chamber Data Pipeline File\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/licenses\": { \"SingularDisplayName\": \"Chamber License\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/storages\": { \"SingularDisplayName\": \"Chamber Storage\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/workloads\": { \"SingularDisplayName\": \"Chamber VM\" }\n ,\"microsoft.modsimworkbench/workbenches/sharedstorages\": { \"SingularDisplayName\": \"Shared Storage\" }\n ,\"microsoft.monitor/accounts\": { \"SingularDisplayName\": \"Azure Monitor workspace\" }\n ,\"microsoft.monitor/investigations\": { \"SingularDisplayName\": \"Microsoft.Monitor investigation\" }\n ,\"microsoft.monitor/pipelinegroups\": { \"SingularDisplayName\": \"Azure Monitor pipeline\" }\n ,\"microsoft.mysqldiscovery/mysqlsites\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsite\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites agent\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites error summary\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/mysqlservers\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites my sqlserver\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/summaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites summary\" }\n ,\"microsoft.netapp/netappaccounts\": { \"SingularDisplayName\": \"NetApp account\" }\n ,\"microsoft.netapp/netappaccounts/backuppolicies\": { \"SingularDisplayName\": \"Backup Policy\" }\n ,\"microsoft.netapp/netappaccounts/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools\": { \"SingularDisplayName\": \"Capacity pool\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes\": { \"SingularDisplayName\": \"Volume\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/volumequotarules\": { \"SingularDisplayName\": \"User and group quota\" }\n ,\"microsoft.netapp/netappaccounts/snapshotpolicies\": { \"SingularDisplayName\": \"Snapshot policy\" }\n ,\"microsoft.netapp/netappaccounts/volumegroups\": { \"SingularDisplayName\": \"VolumeGroup\" }\n ,\"microsoft.network/applicationgatewayavailablessloptions\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl option\" }\n ,\"microsoft.network/applicationgatewayavailablessloptions/predefinedpolicies\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl options predefined policy\" }\n ,\"microsoft.network/applicationgateways\": { \"SingularDisplayName\": \"Application gateway\" }\n ,\"microsoft.network/applicationgatewaywebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Application Gateway WAF policy\" }\n ,\"microsoft.network/applicationsecuritygroups\": { \"SingularDisplayName\": \"Application security group\" }\n ,\"microsoft.network/azurefirewalls\": { \"SingularDisplayName\": \"Firewall\" }\n ,\"microsoft.network/azurewebcategories\": { \"SingularDisplayName\": \"Microsoft.Network Azure web category\" }\n ,\"microsoft.network/bastionhosts\": { \"SingularDisplayName\": \"Bastion\" }\n ,\"microsoft.network/cloudserviceslots\": { \"SingularDisplayName\": \"Microsoft.Network cloud service slot\" }\n ,\"microsoft.network/connections\": { \"SingularDisplayName\": \"Connection\" }\n ,\"microsoft.network/customipprefixes\": { \"SingularDisplayName\": \"Custom IP Prefix\" }\n ,\"microsoft.network/ddoscustompolicies\": { \"SingularDisplayName\": \"Microsoft.Network DDoS custom policy\" }\n ,\"microsoft.network/ddosprotectionplans\": { \"SingularDisplayName\": \"DDoS protection plan\" }\n ,\"microsoft.network/dnsforwardingrulesets\": { \"SingularDisplayName\": \"DNS forwarding ruleset\" }\n ,\"microsoft.network/dnsresolverdomainlists\": { \"SingularDisplayName\": \"DNS Domain List\" }\n ,\"microsoft.network/dnsresolverpolicies\": { \"SingularDisplayName\": \"DNS Security Policy\" }\n ,\"microsoft.network/dnsresolvers\": { \"SingularDisplayName\": \"DNS private resolver\" }\n ,\"microsoft.network/dnszones\": { \"SingularDisplayName\": \"DNS zone\" }\n ,\"microsoft.network/dscpconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network DSCP configuration\" }\n ,\"microsoft.network/expressroutecircuits\": { \"SingularDisplayName\": \"ExpressRoute circuit\" }\n ,\"microsoft.network/expressroutecrossconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connection\" }\n ,\"microsoft.network/expressroutecrossconnections/peerings\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connections peering\" }\n ,\"microsoft.network/expressroutegateways\": { \"SingularDisplayName\": \"ExpressRoute Gateway\" }\n ,\"microsoft.network/expressroutegateways/expressrouteconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route gateways express route connection\" }\n ,\"microsoft.network/expressrouteports\": { \"SingularDisplayName\": \"ExpressRoute Direct\" }\n ,\"microsoft.network/expressrouteportslocations\": { \"SingularDisplayName\": \"Microsoft.Network express route ports location\" }\n ,\"microsoft.network/firewallpolicies\": { \"SingularDisplayName\": \"Firewall Policy\" }\n ,\"microsoft.network/frontdoors\": { \"SingularDisplayName\": \"Front Door and CDN profiles\" }\n ,\"microsoft.network/frontdoorwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Front Door WAF policy\" }\n ,\"microsoft.network/ipallocations\": { \"SingularDisplayName\": \"Microsoft.Network IP allocation\" }\n ,\"microsoft.network/ipgroups\": { \"SingularDisplayName\": \"IP Group\" }\n ,\"microsoft.network/loadbalancers\": { \"SingularDisplayName\": \"Load balancer\" }\n ,\"microsoft.network/localnetworkgateways\": { \"SingularDisplayName\": \"Local network gateway\" }\n ,\"microsoft.network/natgateways\": { \"SingularDisplayName\": \"NAT gateway\" }\n ,\"microsoft.network/networkexperimentprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profile\" }\n ,\"microsoft.network/networkexperimentprofiles/experiments\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profiles experiment\" }\n ,\"microsoft.network/networkinterfaces\": { \"SingularDisplayName\": \"Network interface\" }\n ,\"microsoft.network/networkmanagerconnections\": { \"SingularDisplayName\": \"Microsoft.Network network manager connection\" }\n ,\"microsoft.network/networkmanagers\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/connectivityconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/ipampools\": { \"SingularDisplayName\": \"IP address pool\" }\n ,\"microsoft.network/networkmanagers/networkgroups\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/routingconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/securityadminconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/securityuserconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/verifierworkspaces\": { \"SingularDisplayName\": \"Verifier Workspace\" }\n ,\"microsoft.network/networkprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network profile\" }\n ,\"microsoft.network/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group\" }\n ,\"microsoft.network/networksecurityperimeters\": { \"SingularDisplayName\": \"Network Security Perimeter\" }\n ,\"microsoft.network/networksecurityperimeters/profiles\": { \"SingularDisplayName\": \"Network Security Perimeter Profile\" }\n ,\"microsoft.network/networkverifiers\": { \"SingularDisplayName\": \"Virtual Network Verifier\" }\n ,\"microsoft.network/networkvirtualappliances\": { \"SingularDisplayName\": \"Microsoft.Network network virtual appliance\" }\n ,\"microsoft.network/networkwatchers\": { \"SingularDisplayName\": \"Network Watcher\" }\n ,\"microsoft.network/networkwatchers/flowlogs\": { \"SingularDisplayName\": \"Flow log\" }\n ,\"microsoft.network/p2svpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Point to Site)\" }\n ,\"microsoft.network/privatednszones\": { \"SingularDisplayName\": \"Private DNS zone\" }\n ,\"microsoft.network/privatednszones/virtualnetworklinks\": { \"SingularDisplayName\": \"Virtual network link\" }\n ,\"microsoft.network/privateendpoints\": { \"SingularDisplayName\": \"Private endpoint\" }\n ,\"microsoft.network/privatelinkservices\": { \"SingularDisplayName\": \"Private link service\" }\n ,\"microsoft.network/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\n ,\"microsoft.network/publicipprefixes\": { \"SingularDisplayName\": \"Public IP Prefix\" }\n ,\"microsoft.network/routefilters\": { \"SingularDisplayName\": \"Route filter\" }\n ,\"microsoft.network/routetables\": { \"SingularDisplayName\": \"Route table\" }\n ,\"microsoft.network/securitypartnerproviders\": { \"SingularDisplayName\": \"Microsoft.Network security partner provider\" }\n ,\"microsoft.network/serviceendpointpolicies\": { \"SingularDisplayName\": \"Service endpoint policy\" }\n ,\"microsoft.network/trafficmanagergeographichierarchies\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager geographic hierarchy\" }\n ,\"microsoft.network/trafficmanagerprofiles\": { \"SingularDisplayName\": \"Traffic Manager profile\" }\n ,\"microsoft.network/trafficmanagerusermetricskeys\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager user metrics key\" }\n ,\"microsoft.network/virtualhubs\": { \"SingularDisplayName\": \"Microsoft.Network/virtualHub\" }\n ,\"microsoft.network/virtualnetworkgateways\": { \"SingularDisplayName\": \"Virtual network gateway\" }\n ,\"microsoft.network/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network\" }\n ,\"microsoft.network/virtualnetworktaps\": { \"SingularDisplayName\": \"Virtual network terminal access point\" }\n ,\"microsoft.network/virtualrouters\": { \"SingularDisplayName\": \"Microsoft.Network virtual router\" }\n ,\"microsoft.network/virtualrouters/peerings\": { \"SingularDisplayName\": \"Microsoft.Network virtual routers peering\" }\n ,\"microsoft.network/virtualwans\": { \"SingularDisplayName\": \"Virtual WAN\" }\n ,\"microsoft.network/vpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Site to Site)\" }\n ,\"microsoft.network/vpngateways/vpnconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connection\" }\n ,\"microsoft.network/vpngateways/vpnconnections/vpnlinkconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connections VPN link connection\" }\n ,\"microsoft.network/vpnserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network VPN server configuration\" }\n ,\"microsoft.network/vpnsites\": { \"SingularDisplayName\": \"Microsoft.Network VPN site\" }\n ,\"microsoft.network/vpnsites/vpnsitelinks\": { \"SingularDisplayName\": \"Microsoft.Network VPN sites VPN site link\" }\n ,\"microsoft.networkanalytics/dataconnectors\": { \"SingularDisplayName\": \"AIOps - Data Connector\" }\n ,\"microsoft.networkanalytics/datalakehouses\": { \"SingularDisplayName\": \"AIOps - Data LakeHouse\" }\n ,\"microsoft.networkanalytics/dataproducts\": { \"SingularDisplayName\": \"Azure Operator Insights ? Data Product\" }\n ,\"microsoft.networkanalytics/dataproducts/datatypes\": { \"SingularDisplayName\": \"Data Type\" }\n ,\"microsoft.networkanalytics/dataproductscatalogs\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics data products catalog\" }\n ,\"microsoft.networkanalytics/metricsingestionendpoints\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics metrics ingestion endpoint\" }\n ,\"microsoft.networkanalytics/networkanalyticsproducts\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics network analytics product\" }\n ,\"microsoft.networkcloud/baremetalmachines\": { \"SingularDisplayName\": \"Bare Metal Machine (Operator Nexus)\" }\n ,\"microsoft.networkcloud/cloudservicesnetworks\": { \"SingularDisplayName\": \"Cloud Services Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clustermanagers\": { \"SingularDisplayName\": \"Cluster Manager (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters\": { \"SingularDisplayName\": \"Cluster (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/baremetalmachinekeysets\": { \"SingularDisplayName\": \"Cluster Bare Metal Machine Key Set (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/bmckeysets\": { \"SingularDisplayName\": \"Cluster Baseboard Management Controller Key Set (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/metricsconfigurations\": { \"SingularDisplayName\": \"Cluster Metrics Configuration (Operator Nexus)\" }\n ,\"microsoft.networkcloud/edgeclustermachineskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster machine SKU\" }\n ,\"microsoft.networkcloud/edgeclusterruntimeversions\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster runtime version\" }\n ,\"microsoft.networkcloud/edgeclusters\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster\" }\n ,\"microsoft.networkcloud/edgeclusters/nodes\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge clusters node\" }\n ,\"microsoft.networkcloud/edgeclusterskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster SKU\" }\n ,\"microsoft.networkcloud/kubernetesclusters\": { \"SingularDisplayName\": \"Kubernetes Cluster (Operator Nexus)\" }\n ,\"microsoft.networkcloud/kubernetesclusters/agentpools\": { \"SingularDisplayName\": \"Agent Pool (Operator Nexus)\" }\n ,\"microsoft.networkcloud/kubernetesclusters/features\": { \"SingularDisplayName\": \"Kubernetes Cluster Feature (Operator Nexus)\" }\n ,\"microsoft.networkcloud/l2networks\": { \"SingularDisplayName\": \"Layer 2 Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/l3networks\": { \"SingularDisplayName\": \"Layer 3 Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/racks\": { \"SingularDisplayName\": \"Compute Rack (Operator Nexus)\" }\n ,\"microsoft.networkcloud/rackskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud rack SKU\" }\n ,\"microsoft.networkcloud/registrationhubs\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hub\" }\n ,\"microsoft.networkcloud/registrationhubs/images\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs image\" }\n ,\"microsoft.networkcloud/registrationhubs/machines\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs machine\" }\n ,\"microsoft.networkcloud/storageappliances\": { \"SingularDisplayName\": \"Storage Appliance (Operator Nexus)\" }\n ,\"microsoft.networkcloud/trunkednetworks\": { \"SingularDisplayName\": \"Trunked Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/virtualmachines\": { \"SingularDisplayName\": \"Virtual Machine (Operator Nexus)\" }\n ,\"microsoft.networkcloud/virtualmachines/consoles\": { \"SingularDisplayName\": \"Virtual Machine Console (Operator Nexus)\" }\n ,\"microsoft.networkcloud/volumes\": { \"SingularDisplayName\": \"Volume (Operator Nexus)\" }\n ,\"microsoft.networkfunction/azuretrafficcollectors\": { \"SingularDisplayName\": \"ExpressRoute traffic collector\" }\n ,\"microsoft.networkfunction/meshvpns\": { \"SingularDisplayName\": \"Mesh VPN\" }\n ,\"microsoft.nexusidentity/identitycontrollers\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity controller\" }\n ,\"microsoft.nexusidentity/identitysets\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity set\" }\n ,\"microsoft.notebooks/notebookproxies\": { \"SingularDisplayName\": \"Microsoft.Notebooks notebook proxy\" }\n ,\"microsoft.notificationhubs/namespaces\": { \"SingularDisplayName\": \"Notification Hub Namespace\" }\n ,\"microsoft.notificationhubs/namespaces/notificationhubs\": { \"SingularDisplayName\": \"Notification Hub\" }\n ,\"microsoft.objectstore/osnamespaces\": { \"SingularDisplayName\": \"Microsoft.ObjectStore os namespace\" }\n })[tolower(id)]\n}\n", + "$fxv#3": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_4(id: string) {\n dynamic({\n \"microsoft.offazure/hypervsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv site\" }\n ,\"microsoft.offazure/hypervsites/clusters\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites cluster\" }\n ,\"microsoft.offazure/hypervsites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites host\" }\n ,\"microsoft.offazure/hypervsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites job\" }\n ,\"microsoft.offazure/hypervsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machine\" }\n ,\"microsoft.offazure/hypervsites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machines software inventory\" }\n ,\"microsoft.offazure/hypervsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites operations statu\" }\n ,\"microsoft.offazure/hypervsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites run as account\" }\n ,\"microsoft.offazure/importsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure import site\" }\n ,\"microsoft.offazure/importsites/deletejobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites delete job\" }\n ,\"microsoft.offazure/importsites/exportjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites export job\" }\n ,\"microsoft.offazure/importsites/importjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites import job\" }\n ,\"microsoft.offazure/importsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites job\" }\n ,\"microsoft.offazure/importsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites machine\" }\n ,\"microsoft.offazure/mastersites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master site\" }\n ,\"microsoft.offazure/mastersites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites operations statu\" }\n ,\"microsoft.offazure/mastersites/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private endpoint connection\" }\n ,\"microsoft.offazure/mastersites/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private link resource\" }\n ,\"microsoft.offazure/mastersites/sqlsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql site\" }\n ,\"microsoft.offazure/mastersites/sqlsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites discovery site data source\" }\n ,\"microsoft.offazure/mastersites/sqlsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites job\" }\n ,\"microsoft.offazure/mastersites/sqlsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites operations statu\" }\n ,\"microsoft.offazure/mastersites/sqlsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites run as account\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqlavailabilitygroups\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql availability group\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqldatabases\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql database\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqlservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql server\" }\n ,\"microsoft.offazure/mastersites/webappsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app site\" }\n ,\"microsoft.offazure/mastersites/webappsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites discovery site data source\" }\n ,\"microsoft.offazure/mastersites/webappsites/extendedmachines\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites extended machine\" }\n ,\"microsoft.offazure/mastersites/webappsites/iiswebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web application\" }\n ,\"microsoft.offazure/mastersites/webappsites/iiswebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web server\" }\n ,\"microsoft.offazure/mastersites/webappsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites runasaccount\" }\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web application\" }\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web server\" }\n ,\"microsoft.offazure/serversites\": { \"SingularDisplayName\": \"Microsoft.OffAzure server site\" }\n ,\"microsoft.offazure/serversites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites job\" }\n ,\"microsoft.offazure/serversites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machine\" }\n ,\"microsoft.offazure/serversites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machines software inventory\" }\n ,\"microsoft.offazure/serversites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites operations statu\" }\n ,\"microsoft.offazure/serversites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites run as account\" }\n ,\"microsoft.offazure/vmwaresites\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware site\" }\n ,\"microsoft.offazure/vmwaresites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites host\" }\n ,\"microsoft.offazure/vmwaresites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites job\" }\n ,\"microsoft.offazure/vmwaresites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machine\" }\n ,\"microsoft.offazure/vmwaresites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machines software inventory\" }\n ,\"microsoft.offazure/vmwaresites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites operations statu\" }\n ,\"microsoft.offazure/vmwaresites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites run as account\" }\n ,\"microsoft.offazure/vmwaresites/vcenters\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites vcenter\" }\n ,\"microsoft.offazurespringboot/springbootsites\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsite\" }\n ,\"microsoft.offazurespringboot/springbootsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites error summary\" }\n ,\"microsoft.offazurespringboot/springbootsites/springbootapps\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootapp\" }\n ,\"microsoft.offazurespringboot/springbootsites/springbootservers\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootserver\" }\n ,\"microsoft.offazurespringboot/springbootsites/summaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites summary\" }\n ,\"microsoft.onlineexperimentation/workspaces\": { \"SingularDisplayName\": \"Online Experimentation Workspace\" }\n ,\"microsoft.openenergyplatform/energyservices\": { \"SingularDisplayName\": \"Azure Data Manager for Energy\" }\n ,\"microsoft.openlogisticsplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspace\" }\n ,\"microsoft.openlogisticsplatform/workspaces/applicationregistrations\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application registration\" }\n ,\"microsoft.openlogisticsplatform/workspaces/applications\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application\" }\n ,\"microsoft.openlogisticsplatform/workspaces/eventgridfilters\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces event grid filter\" }\n ,\"microsoft.openlogisticsplatform/workspaces/shares\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share\" }\n ,\"microsoft.openlogisticsplatform/workspaces/sharesubscriptions\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share subscription\" }\n ,\"microsoft.operationalinsights/clusters\": { \"SingularDisplayName\": \"Log Analytics dedicated cluster\" }\n ,\"microsoft.operationalinsights/querypacks\": { \"SingularDisplayName\": \"Log Analytics query pack\" }\n ,\"microsoft.operationalinsights/workspaces\": { \"SingularDisplayName\": \"Log Analytics workspace\" }\n ,\"microsoft.operationsmanagement/managementassociations\": { \"SingularDisplayName\": \"Microsoft.OperationsManagement management association\" }\n ,\"microsoft.operationsmanagement/solutions\": { \"SingularDisplayName\": \"Solution\" }\n ,\"microsoft.operatorvoicemail/operatorvoicemailinstances\": { \"SingularDisplayName\": \"Microsoft.OperatorVoicemail operator voicemail instance\" }\n ,\"microsoft.oraclediscovery/oraclesites\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle site\" }\n ,\"microsoft.oraclediscovery/oraclesites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites error summary\" }\n ,\"microsoft.oraclediscovery/oraclesites/oracledatabases\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle database\" }\n ,\"microsoft.oraclediscovery/oraclesites/oracleservers\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle server\" }\n ,\"microsoft.oraclediscovery/oraclesites/summaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites summary\" }\n ,\"microsoft.orbital/cloudaccessrouters\": { \"SingularDisplayName\": \"Cloud Access Router\" }\n ,\"microsoft.orbital/contactprofiles\": { \"SingularDisplayName\": \"Contact Profile\" }\n ,\"microsoft.orbital/edgesites\": { \"SingularDisplayName\": \"Edge Site\" }\n ,\"microsoft.orbital/geocatalogs\": { \"SingularDisplayName\": \"GeoCatalog\" }\n ,\"microsoft.orbital/globalcommunicationssites\": { \"SingularDisplayName\": \"Microsoft.Orbital global communications site\" }\n ,\"microsoft.orbital/groundstations\": { \"SingularDisplayName\": \"Ground Station\" }\n ,\"microsoft.orbital/l2connections\": { \"SingularDisplayName\": \"L2 Connection\" }\n ,\"microsoft.orbital/sdwancontrollers\": { \"SingularDisplayName\": \"SDWAN Controller\" }\n ,\"microsoft.orbital/spacecrafts\": { \"SingularDisplayName\": \"Spacecraft\" }\n ,\"microsoft.orbital/spacecrafts/contacts\": { \"SingularDisplayName\": \"Contact\" }\n ,\"microsoft.orbital/terminals\": { \"SingularDisplayName\": \"Cloud Access Terminal\" }\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrence\" }\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences/operationresult\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrences operation result\" }\n ,\"microsoft.peering/peerasns\": { \"SingularDisplayName\": \"Microsoft.Peering peer asn\" }\n ,\"microsoft.peering/peerings\": { \"SingularDisplayName\": \"Peering\" }\n ,\"microsoft.peering/peerings/registeredasns\": { \"SingularDisplayName\": \"Registered ASN\" }\n ,\"microsoft.peering/peerings/registeredprefixes\": { \"SingularDisplayName\": \"Registered prefix\" }\n ,\"microsoft.peering/peeringservices\": { \"SingularDisplayName\": \"Peering Service\" }\n ,\"microsoft.peering/peeringservices/prefixes\": { \"SingularDisplayName\": \"Peering Service Prefix\" }\n ,\"microsoft.pki/pkis\": { \"SingularDisplayName\": \"Microsoft.Pki PKI\" }\n ,\"microsoft.pki/pkis/certificateauthorities\": { \"SingularDisplayName\": \"Microsoft.Pki pkis certificate authority\" }\n ,\"microsoft.pki/pkis/enrollmentpolicies\": { \"SingularDisplayName\": \"Microsoft.Pki pkis enrollment policy\" }\n ,\"microsoft.policyinsights/attestations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights attestation\" }\n ,\"microsoft.policyinsights/policymetadata\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights policy metadata\" }\n ,\"microsoft.policyinsights/remediations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights remediation\" }\n ,\"microsoft.portal/consoles\": { \"SingularDisplayName\": \"Microsoft.Portal console\" }\n ,\"microsoft.portal/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\n ,\"microsoft.portal/tenantconfigurations\": { \"SingularDisplayName\": \"Microsoft.Portal tenant configuration\" }\n ,\"microsoft.portal/usersettings\": { \"SingularDisplayName\": \"Microsoft.Portal user setting\" }\n ,\"microsoft.portal/virtual-privatedashboards\": { \"SingularDisplayName\": \"Private dashboard\" }\n ,\"microsoft.portalservices/copilotsettings\": { \"SingularDisplayName\": \"Microsoft.PortalServices copilot setting\" }\n ,\"microsoft.portalservices/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\n ,\"microsoft.portalservices/extensions\": { \"SingularDisplayName\": \"Portal Extension\" }\n ,\"microsoft.portalservices/extensions/deployments\": { \"SingularDisplayName\": \"Extension Deployment\" }\n ,\"microsoft.portalservices/extensions/slots\": { \"SingularDisplayName\": \"Extension Slot\" }\n ,\"microsoft.portalservices/extensions/versions\": { \"SingularDisplayName\": \"Extension Version\" }\n ,\"microsoft.portalservices/settings\": { \"SingularDisplayName\": \"Microsoft.PortalServices setting\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private endpoint connection\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private link resource\" }\n ,\"microsoft.powerbi/workspacecollections\": { \"SingularDisplayName\": \"Microsoft.PowerBI workspace collection\" }\n ,\"microsoft.powerbidedicated/autoscalevcores\": { \"SingularDisplayName\": \"Microsoft.PowerBIDedicated auto scale vcore\" }\n ,\"microsoft.powerbidedicated/capacities\": { \"SingularDisplayName\": \"Power BI Embedded\" }\n ,\"microsoft.powerplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.PowerPlatform account\" }\n ,\"microsoft.premonition/libraries\": { \"SingularDisplayName\": \"Microsoft.Premonition library\" }\n ,\"microsoft.premonition/libraries/analyses\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries analyse\" }\n ,\"microsoft.premonition/libraries/samples\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries sample\" }\n ,\"microsoft.professionalservice/resources\": { \"SingularDisplayName\": \"Professional Service\" }\n ,\"microsoft.programmableconnectivity/gateways\": { \"SingularDisplayName\": \"APC Gateway\" }\n ,\"microsoft.programmableconnectivity/operatorapiconnections\": { \"SingularDisplayName\": \"APC Operator API Connection\" }\n ,\"microsoft.programmableconnectivity/operatorapiplans\": { \"SingularDisplayName\": \"APC Operator API Plan\" }\n ,\"microsoft.proposal/proposals\": { \"SingularDisplayName\": \"Microsoft.Proposal proposal\" }\n ,\"microsoft.providerhub/providerregistrations\": { \"SingularDisplayName\": \"Resource Provider as a Service\" }\n ,\"microsoft.providerhub/providerregistrations/customrollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.providerhub/providerregistrations/defaultrollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\n ,\"microsoft.providerhubdevtest/regionalstresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest regional stresstest\" }\n ,\"microsoft.providerhubdevtest/stresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest stresstest\" }\n ,\"microsoft.purview/accounts\": { \"SingularDisplayName\": \"Microsoft Purview account\" }\n ,\"microsoft.quantum/provideraccounts\": { \"SingularDisplayName\": \"Microsoft.Quantum provider account\" }\n ,\"microsoft.quantum/workspaces\": { \"SingularDisplayName\": \"Quantum Workspace\" }\n ,\"microsoft.quota/groupquotas\": { \"SingularDisplayName\": \"Microsoft.Quota group quota\" }\n ,\"microsoft.quota/groupquotas/groupquotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas group quota request\" }\n ,\"microsoft.quota/groupquotas/quotaallocationrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation request\" }\n ,\"microsoft.quota/groupquotas/quotaallocations\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation\" }\n ,\"microsoft.quota/groupquotas/subscriptionrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription request\" }\n ,\"microsoft.quota/groupquotas/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription\" }\n ,\"microsoft.quota/quotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota quota request\" }\n ,\"microsoft.quota/quotas\": { \"SingularDisplayName\": \"Microsoft.Quota quota\" }\n ,\"microsoft.quota/usages\": { \"SingularDisplayName\": \"Microsoft.Quota usage\" }\n ,\"microsoft.recommendationsservice/accounts\": { \"SingularDisplayName\": \"Intelligent Recommendations Account\" }\n ,\"microsoft.recommendationsservice/accounts/modeling\": { \"SingularDisplayName\": \"Modeling\" }\n ,\"microsoft.recommendationsservice/accounts/serviceendpoints\": { \"SingularDisplayName\": \"Service Endpoint\" }\n ,\"microsoft.recoveryservices/replicationeligibilityresults\": { \"SingularDisplayName\": \"Microsoft.RecoveryServices replication eligibility result\" }\n ,\"microsoft.recoveryservices/vaults\": { \"SingularDisplayName\": \"Recovery Services vault\" }\n ,\"microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems\": { \"SingularDisplayName\": \"Backup Item\" }\n ,\"microsoft.recoveryservicesbvtd/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD\" }\n ,\"microsoft.recoveryservicesbvtd2/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD2\" }\n ,\"microsoft.recoveryservicesintd/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD\" }\n ,\"microsoft.recoveryservicesintd2/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD2\" }\n ,\"microsoft.redhatopenshift/openshiftclusters\": { \"SingularDisplayName\": \"Azure Red Hat OpenShift cluster\" }\n ,\"microsoft.relationships/dependencyof\": { \"SingularDisplayName\": \"Dependency Relationship\" }\n ,\"microsoft.relationships/servicegroupmember\": { \"SingularDisplayName\": \"Service group member relationship\" }\n ,\"microsoft.relationships/servicegrouprelationships\": { \"SingularDisplayName\": \"Connected Resource\" }\n ,\"microsoft.relay/namespaces\": { \"SingularDisplayName\": \"Relay\" }\n ,\"microsoft.relay/namespaces/hybridconnections\": { \"SingularDisplayName\": \"Hybrid connection\" }\n ,\"microsoft.relay/namespaces/wcfrelays\": { \"SingularDisplayName\": \"WCF relay\" }\n ,\"microsoft.resilience/resiliencestates\": { \"SingularDisplayName\": \"Microsoft.Resilience resilience state\" }\n ,\"microsoft.resourceconnector/appliances\": { \"SingularDisplayName\": \"Resource bridge\" }\n ,\"microsoft.resourcegraph/queries\": { \"SingularDisplayName\": \"Resource Graph query\" }\n ,\"microsoft.resourcehealth/availabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth availability statuse\" }\n ,\"microsoft.resourcehealth/childavailabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth child availability statuse\" }\n ,\"microsoft.resourcehealth/emergingissues\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth emerging issue\" }\n ,\"microsoft.resourcehealth/events\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth event\" }\n ,\"microsoft.resourcehealth/events/impactedresources\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth events impacted resource\" }\n ,\"microsoft.resourcehealth/metadata\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth metadata\" }\n ,\"microsoft.resources/builtintemplatespecs\": { \"SingularDisplayName\": \"Built-in template spec\" }\n ,\"microsoft.resources/changes\": { \"SingularDisplayName\": \"Microsoft.Resources change\" }\n ,\"microsoft.resources/databoundaries\": { \"SingularDisplayName\": \"Microsoft.Resources data boundary\" }\n ,\"microsoft.resources/deletedresources\": { \"SingularDisplayName\": \"Recycle Bin\" }\n ,\"microsoft.resources/deployments\": { \"SingularDisplayName\": \"Microsoft.Resources deployment\" }\n ,\"microsoft.resources/deployments/operations\": { \"SingularDisplayName\": \"Microsoft.Resources deployments operation\" }\n ,\"microsoft.resources/deploymentscripts\": { \"SingularDisplayName\": \"Deployment Script\" }\n ,\"microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\n ,\"microsoft.resources/mobobrokers\": { \"SingularDisplayName\": \"Microsoft.Resources mobo broker\" }\n ,\"microsoft.resources/resourcechange\": { \"SingularDisplayName\": \"Change Analysis\" }\n ,\"microsoft.resources/resourcechanges\": { \"SingularDisplayName\": \"Resource change\" }\n ,\"microsoft.resources/resourcegraphvisualizer\": { \"SingularDisplayName\": \"Resource Graph Visualizer\" }\n ,\"microsoft.resources/resourcegroups\": { \"SingularDisplayName\": \"Microsoft.Resources resource group\" }\n ,\"microsoft.resources/resources\": { \"SingularDisplayName\": \"Resource\" }\n ,\"microsoft.resources/snapshots\": { \"SingularDisplayName\": \"Microsoft.Resources snapshot\" }\n ,\"microsoft.resources/subscriptions\": { \"SingularDisplayName\": \"Subscription\" }\n ,\"microsoft.resources/subscriptions/resourcegroups\": { \"SingularDisplayName\": \"Resource group\" }\n ,\"microsoft.resources/tags\": { \"SingularDisplayName\": \"Microsoft.Resources tag\" }\n ,\"microsoft.resources/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\n ,\"microsoft.resources/virtualsubscriptionsforresourcepicker\": { \"SingularDisplayName\": \"Subscription\" }\n ,\"microsoft.saas/applications\": { \"SingularDisplayName\": \"Software as a Service (classic)\" }\n ,\"microsoft.saas/resources\": { \"SingularDisplayName\": \"SaaS\" }\n ,\"microsoft.saas/saasresources\": { \"SingularDisplayName\": \"SaaS (classic)\" }\n ,\"microsoft.saashub/cloudservices\": { \"SingularDisplayName\": \"Microsoft.SaaSHub cloud service\" }\n ,\"microsoft.saashub/cloudservices/hidden\": { \"SingularDisplayName\": \"Microsoft SaaS\" }\n ,\"microsoft.saashub/saasresources\": { \"SingularDisplayName\": \"Microsoft.SaaSHub saas resource\" }\n ,\"microsoft.salescopilot/conversationintelligencerecordingaccounts\": { \"SingularDisplayName\": \"Microsoft.SalesCopilot conversation intelligence recording account\" }\n ,\"microsoft.scheduler/jobcollections\": { \"SingularDisplayName\": \"Scheduler job collection\" }\n ,\"microsoft.scheduler/jobcollections/jobs\": { \"SingularDisplayName\": \"Scheduler job\" }\n ,\"microsoft.scom/managedinstances\": { \"SingularDisplayName\": \"SCOM managed instance\" }\n ,\"microsoft.scvmm/availabilitysets\": { \"SingularDisplayName\": \"Microsoft.ScVmm availability set\" }\n ,\"microsoft.scvmm/clouds\": { \"SingularDisplayName\": \"Microsoft.ScVmm cloud\" }\n ,\"microsoft.scvmm/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instance\" }\n ,\"microsoft.scvmm/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances guest agent\" }\n ,\"microsoft.scvmm/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.scvmm/virtualmachines\": { \"SingularDisplayName\": \"SCVMM virtual machine - Azure Arc\" }\n ,\"microsoft.scvmm/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine template\" }\n ,\"microsoft.scvmm/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual network\" }\n ,\"microsoft.scvmm/vmmservers\": { \"SingularDisplayName\": \"SCVMM management server\" }\n ,\"microsoft.search/searchservices\": { \"SingularDisplayName\": \"Search service\" }\n ,\"microsoft.secretmanagementsampleprovider/forecasts\": { \"SingularDisplayName\": \"Microsoft.SecretManagementSampleProvider forecast\" }\n ,\"microsoft.secretsynccontroller/azurekeyvaultsecretproviderclasses\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController Azure key vault secret provider class\" }\n ,\"microsoft.secretsynccontroller/secretsyncs\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController secret sync\" }\n ,\"microsoft.security/adaptivenetworkhardenings\": { \"SingularDisplayName\": \"Microsoft.Security adaptive network hardening\" }\n ,\"microsoft.security/advancedthreatprotectionsettings\": { \"SingularDisplayName\": \"Microsoft.Security advanced threat protection setting\" }\n ,\"microsoft.security/alertssuppressionrules\": { \"SingularDisplayName\": \"Microsoft.Security alerts suppression rule\" }\n ,\"microsoft.security/apicollections\": { \"SingularDisplayName\": \"Microsoft.Security API collection\" }\n ,\"microsoft.security/applications\": { \"SingularDisplayName\": \"Microsoft.Security application\" }\n ,\"microsoft.security/assessmentmetadata\": { \"SingularDisplayName\": \"Microsoft.Security assessment metadata\" }\n ,\"microsoft.security/assessments\": { \"SingularDisplayName\": \"Microsoft.Security assessment\" }\n ,\"microsoft.security/assessments/governanceassignments\": { \"SingularDisplayName\": \"Microsoft.Security assessments governance assignment\" }\n ,\"microsoft.security/assessments/subassessments\": { \"SingularDisplayName\": \"Microsoft.Security assessments sub assessment\" }\n ,\"microsoft.security/assignments\": { \"SingularDisplayName\": \"Microsoft.Security assignment\" }\n ,\"microsoft.security/automations\": { \"SingularDisplayName\": \"Microsoft.Security automation\" }\n ,\"microsoft.security/autoprovisioningsettings\": { \"SingularDisplayName\": \"Microsoft.Security auto provisioning setting\" }\n ,\"microsoft.security/complianceresults\": { \"SingularDisplayName\": \"Microsoft.Security compliance result\" }\n ,\"microsoft.security/compliances\": { \"SingularDisplayName\": \"Microsoft.Security compliance\" }\n ,\"microsoft.security/connectors\": { \"SingularDisplayName\": \"Microsoft.Security connector\" }\n ,\"microsoft.security/customassessmentautomations\": { \"SingularDisplayName\": \"Microsoft.Security custom assessment automation\" }\n ,\"microsoft.security/defenderforstoragesettings\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage setting\" }\n ,\"microsoft.security/defenderforstoragesettings/malwarescans\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage settings malware scan\" }\n ,\"microsoft.security/devicesecuritygroups\": { \"SingularDisplayName\": \"Microsoft.Security device security group\" }\n ,\"microsoft.security/governancerules\": { \"SingularDisplayName\": \"Microsoft.Security governance rule\" }\n ,\"microsoft.security/governancerules/operationresults\": { \"SingularDisplayName\": \"Microsoft.Security governance rules operation result\" }\n ,\"microsoft.security/healthreports\": { \"SingularDisplayName\": \"Microsoft.Security health report\" }\n ,\"microsoft.security/informationprotectionpolicies\": { \"SingularDisplayName\": \"Microsoft.Security information protection policy\" }\n ,\"microsoft.security/iotsecuritysolutions\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solution\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics model\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated alert\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated recommendation\" }\n ,\"microsoft.security/iotsecuritysolutions/iotalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert\" }\n ,\"microsoft.security/iotsecuritysolutions/iotalerttypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert type\" }\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation\" }\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendationtypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation type\" }\n ,\"microsoft.security/locations/alerts\": { \"SingularDisplayName\": \"Security Alert\" }\n ,\"microsoft.security/mdeonboardings\": { \"SingularDisplayName\": \"Microsoft.Security mde onboarding\" }\n ,\"microsoft.security/pricings\": { \"SingularDisplayName\": \"Defender for Cloud\" }\n ,\"microsoft.security/pricings/securityoperators\": { \"SingularDisplayName\": \"Microsoft.Security pricings security operator\" }\n ,\"microsoft.security/regulatorycompliancestandards\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standard\" }\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance control\" }\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols/regulatorycomplianceassessments\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance controls regulatory compliance assessment\" }\n ,\"microsoft.security/securescores\": { \"SingularDisplayName\": \"Microsoft.Security secure score\" }\n ,\"microsoft.security/securityconnectors\": { \"SingularDisplayName\": \"Microsoft.Security security connector\" }\n ,\"microsoft.security/securityconnectors/devops\": { \"SingularDisplayName\": \"Microsoft.Security security connectors devop\" }\n ,\"microsoft.security/securitycontacts\": { \"SingularDisplayName\": \"Microsoft.Security security contact\" }\n ,\"microsoft.security/sensitivitysettings\": { \"SingularDisplayName\": \"Microsoft.Security sensitivity setting\" }\n ,\"microsoft.security/servervulnerabilityassessments\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessment\" }\n ,\"microsoft.security/servervulnerabilityassessmentssettings\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessments setting\" }\n ,\"microsoft.security/settings\": { \"SingularDisplayName\": \"Microsoft.Security setting\" }\n ,\"microsoft.security/standards\": { \"SingularDisplayName\": \"Microsoft.Security standard\" }\n ,\"microsoft.security/workspacesettings\": { \"SingularDisplayName\": \"Microsoft.Security workspace setting\" }\n ,\"microsoft.securitycopilot/capacities\": { \"SingularDisplayName\": \"Microsoft Security compute capacity\" }\n ,\"microsoft.securitydetonation/chambers\": { \"SingularDisplayName\": \"Security Detonation Chamber\" }\n ,\"microsoft.securityinsightsarg/sentinel\": { \"SingularDisplayName\": \"Microsoft Sentinel\" }\n ,\"microsoft.sentinelplatformservices/sentinelplatformservices\": { \"SingularDisplayName\": \"Microsoft.SentinelPlatformServices sentinel platform service\" }\n ,\"microsoft.serialconsole/consoleservices\": { \"SingularDisplayName\": \"Microsoft.SerialConsole console service\" }\n ,\"microsoft.serialconsole/serialports\": { \"SingularDisplayName\": \"Microsoft.SerialConsole serial port\" }\n ,\"microsoft.servicebus/namespaces\": { \"SingularDisplayName\": \"Service Bus namespace\" }\n ,\"microsoft.servicebus/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Service Bus Geo-DR Alias\" }\n ,\"microsoft.servicebus/namespaces/queues\": { \"SingularDisplayName\": \"Service Bus queue\" }\n ,\"microsoft.servicebus/namespaces/topics\": { \"SingularDisplayName\": \"Service Bus topic\" }\n ,\"microsoft.servicebus/namespaces/topics/subscriptions\": { \"SingularDisplayName\": \"Service Bus Subscription\" }\n ,\"microsoft.servicefabric/clusters\": { \"SingularDisplayName\": \"Service Fabric cluster\" }\n ,\"microsoft.servicefabric/managedclusters\": { \"SingularDisplayName\": \"Service Fabric managed cluster\" }\n ,\"microsoft.servicefabricmesh/applications\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh application\" }\n ,\"microsoft.servicefabricmesh/applications/services\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications service\" }\n ,\"microsoft.servicefabricmesh/applications/services/replicas\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications services replica\" }\n ,\"microsoft.servicefabricmesh/gateways\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh gateway\" }\n ,\"microsoft.servicefabricmesh/networks\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh network\" }\n ,\"microsoft.servicefabricmesh/secrets\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secret\" }\n ,\"microsoft.servicefabricmesh/secrets/values\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secrets value\" }\n ,\"microsoft.servicefabricmesh/volumes\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh volume\" }\n ,\"microsoft.servicelinker/dryruns\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker dryrun\" }\n ,\"microsoft.servicelinker/linkers\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker linker\" }\n ,\"microsoft.servicenetworking/trafficcontrollers\": { \"SingularDisplayName\": \"Application Gateway for Containers\" }\n ,\"microsoft.serviceshub/connectors\": { \"SingularDisplayName\": \"Services Hub Connector\" }\n ,\"microsoft.signalrservice/signalr\": { \"SingularDisplayName\": \"SignalR\" }\n ,\"microsoft.signalrservice/signalr/replicas\": { \"SingularDisplayName\": \"SignalR Replica\" }\n ,\"microsoft.signalrservice/webpubsub\": { \"SingularDisplayName\": \"Web PubSub Service\" }\n ,\"microsoft.signalrservice/webpubsub/replicas\": { \"SingularDisplayName\": \"Web PubSub Service Replica\" }\n ,\"microsoft.skytap/billingnodes\": { \"SingularDisplayName\": \"Microsoft.Skytap billing node\" }\n ,\"microsoft.skytap/interfaces\": { \"SingularDisplayName\": \"Microsoft.Skytap interface\" }\n ,\"microsoft.skytap/nodes\": { \"SingularDisplayName\": \"Microsoft.Skytap node\" }\n ,\"microsoft.softwareplan/hybridusebenefits\": { \"SingularDisplayName\": \"Microsoft.SoftwarePlan hybrid use benefit\" }\n ,\"microsoft.solutions/applicationdefinitions\": { \"SingularDisplayName\": \"Service catalog managed application definition\" }\n ,\"microsoft.solutions/applications\": { \"SingularDisplayName\": \"Managed application\" }\n ,\"microsoft.solutions/jitrequests\": { \"SingularDisplayName\": \"Microsoft.Solutions JIT request\" }\n ,\"microsoft.sovereign/landingzoneaccounts\": { \"SingularDisplayName\": \"Landing zone account\" }\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\n ,\"microsoft.sovereign/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\n ,\"microsoft.sovereign/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\n ,\"microsoft.sovereign/transparencylogs\": { \"SingularDisplayName\": \"Transparency log\" }\n ,\"microsoft.sql/azuresql\": { \"SingularDisplayName\": \"Azure SQL resource\" }\n ,\"microsoft.sql/instancepools\": { \"SingularDisplayName\": \"Instance pool\" }\n ,\"microsoft.sql/managedinstances\": { \"SingularDisplayName\": \"SQL managed instance\" }\n ,\"microsoft.sql/managedinstances/databases\": { \"SingularDisplayName\": \"Managed database\" }\n ,\"microsoft.sql/servers\": { \"SingularDisplayName\": \"SQL server\" }\n ,\"microsoft.sql/servers/databases\": { \"SingularDisplayName\": \"SQL database\" }\n ,\"microsoft.sql/servers/elasticpools\": { \"SingularDisplayName\": \"SQL elastic pool\" }\n ,\"microsoft.sql/servers/jobagents\": { \"SingularDisplayName\": \"Elastic Job agent\" }\n ,\"microsoft.sql/virtualclusters\": { \"SingularDisplayName\": \"Virtual cluster\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine group\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups/availabilitygrouplisteners\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine groups availability group listener\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachines\": { \"SingularDisplayName\": \"SQL virtual machine\" }\n ,\"microsoft.standbypool/standbycontainergrouppools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pool\" }\n ,\"microsoft.standbypool/standbycontainergrouppools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pools runtime view\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pool\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools runtime view\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools/standbyvirtualmachines\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools standby virtual machine\" }\n ,\"microsoft.storage/storageaccounts\": { \"SingularDisplayName\": \"Storage account\" }\n ,\"microsoft.storageactions/storagetasks\": { \"SingularDisplayName\": \"Storage task - Azure Storage Actions\" }\n ,\"microsoft.storagecache/amlfilesystems\": { \"SingularDisplayName\": \"Azure Managed Lustre\" }\n ,\"microsoft.storagecache/caches\": { \"SingularDisplayName\": \"HPC cache\" }\n ,\"microsoft.storagediscovery/storagediscoveryworkspaces\": { \"SingularDisplayName\": \"Storage Discovery workspace\" }\n ,\"microsoft.storagehub/all\": { \"SingularDisplayName\": \"All resources\" }\n ,\"microsoft.storagehub/policycomplianceresources\": { \"SingularDisplayName\": \"Policy compliance\" }\n ,\"microsoft.storageinsights/storagecollectionrules\": { \"SingularDisplayName\": \"Microsoft.StorageInsights storage collection rule\" }\n ,\"microsoft.storagemover/storagemovers\": { \"SingularDisplayName\": \"Storage mover\" }\n ,\"microsoft.storagepool/diskpools\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pool\" }\n ,\"microsoft.storagepool/diskpools/iscsitargets\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pools iscsi target\" }\n ,\"microsoft.storagesync/storagesyncservices\": { \"SingularDisplayName\": \"Storage Sync Service\" }\n ,\"microsoft.storagetasks/storagetasks\": { \"SingularDisplayName\": \"Microsoft.StorageTasks storage task\" }\n ,\"microsoft.storsimple/managers\": { \"SingularDisplayName\": \"StorSimple device manager\" }\n ,\"microsoft.storsimple/managers/accesscontrolrecords\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers access control record\" }\n ,\"microsoft.storsimple/managers/bandwidthsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers bandwidth setting\" }\n ,\"microsoft.storsimple/managers/certificates\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers certificate\" }\n ,\"microsoft.storsimple/managers/devices\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers device\" }\n ,\"microsoft.storsimple/managers/devices/alertsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices alert setting\" }\n ,\"microsoft.storsimple/managers/devices/backuppolicies\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policy\" }\n ,\"microsoft.storsimple/managers/devices/backuppolicies/schedules\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policies schedule\" }\n ,\"microsoft.storsimple/managers/devices/backupschedulegroups\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup schedule group\" }\n ,\"microsoft.storsimple/managers/devices/chapsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices chap setting\" }\n ,\"microsoft.storsimple/managers/devices/fileservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileserver\" }\n ,\"microsoft.storsimple/managers/devices/fileservers/shares\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileservers share\" }\n ,\"microsoft.storsimple/managers/devices/iscsiservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiserver\" }\n ,\"microsoft.storsimple/managers/devices/iscsiservers/disks\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiservers disk\" }\n ,\"microsoft.storsimple/managers/devices/jobs\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices job\" }\n ,\"microsoft.storsimple/managers/devices/networksettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices network setting\" }\n ,\"microsoft.storsimple/managers/devices/securitysettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices security setting\" }\n ,\"microsoft.storsimple/managers/devices/timesettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices time setting\" }\n ,\"microsoft.storsimple/managers/devices/updatesummary\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices update summary\" }\n ,\"microsoft.storsimple/managers/devices/volumecontainers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume container\" }\n ,\"microsoft.storsimple/managers/devices/volumecontainers/volumes\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume containers volume\" }\n ,\"microsoft.storsimple/managers/encryptionsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers encryption setting\" }\n ,\"microsoft.storsimple/managers/extendedinformation\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers extended information\" }\n ,\"microsoft.storsimple/managers/storageaccountcredentials\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage account credential\" }\n ,\"microsoft.storsimple/managers/storagedomains\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage domain\" }\n ,\"microsoft.streamanalytics/clusters\": { \"SingularDisplayName\": \"Stream Analytics cluster\" }\n ,\"microsoft.streamanalytics/streamingjobs\": { \"SingularDisplayName\": \"Stream Analytics job\" }\n ,\"microsoft.subscription/aliases\": { \"SingularDisplayName\": \"Microsoft.Subscription aliase\" }\n ,\"microsoft.subscription/changetenantrequest\": { \"SingularDisplayName\": \"Microsoft.Subscription change tenant request\" }\n ,\"microsoft.subscription/policies\": { \"SingularDisplayName\": \"Microsoft.Subscription policy\" }\n ,\"microsoft.subscription/subscriptiondefinitions\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription definition\" }\n ,\"microsoft.subscription/subscriptionoperations\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription operation\" }\n ,\"microsoft.support/fileworkspaces\": { \"SingularDisplayName\": \"Microsoft.Support file workspace\" }\n ,\"microsoft.support/fileworkspaces/files\": { \"SingularDisplayName\": \"Microsoft.Support file workspaces file\" }\n ,\"microsoft.support/services\": { \"SingularDisplayName\": \"Microsoft.Support service\" }\n ,\"microsoft.support/services/problemclassifications\": { \"SingularDisplayName\": \"Microsoft.Support services problem classification\" }\n ,\"microsoft.support/supporttickets\": { \"SingularDisplayName\": \"Support Request\" }\n ,\"microsoft.sustainabilityservices/calculations\": { \"SingularDisplayName\": \"Project Sustainability Calculator\" }\n ,\"microsoft.symphony/instances\": { \"SingularDisplayName\": \"Microsoft.Symphony instance\" }\n ,\"microsoft.symphony/solutions\": { \"SingularDisplayName\": \"Microsoft.Symphony solution\" }\n ,\"microsoft.symphony/targets\": { \"SingularDisplayName\": \"Microsoft.Symphony target\" }\n ,\"microsoft.synapse/privatelinkhubs\": { \"SingularDisplayName\": \"Synapse private link hub\" }\n ,\"microsoft.synapse/workspaces\": { \"SingularDisplayName\": \"Synapse workspace\" }\n ,\"microsoft.synapse/workspaces/bigdatapools\": { \"SingularDisplayName\": \"Apache Spark pool\" }\n ,\"microsoft.synapse/workspaces/kustopools\": { \"SingularDisplayName\": \"Data Explorer pool\" }\n ,\"microsoft.synapse/workspaces/kustopools/databases\": { \"SingularDisplayName\": \"Data Explorer Database\" }\n ,\"microsoft.synapse/workspaces/scopepools\": { \"SingularDisplayName\": \"SCOPE pool\" }\n ,\"microsoft.synapse/workspaces/sqlpools\": { \"SingularDisplayName\": \"Dedicated SQL pool\" }\n ,\"microsoft.syntex/accounts\": { \"SingularDisplayName\": \"Microsoft.Syntex account\" }\n ,\"microsoft.syntex/documentprocessors\": { \"SingularDisplayName\": \"Microsoft.Syntex document processor\" }\n ,\"microsoft.test/healthdataaiservices\": { \"SingularDisplayName\": \"Azure Health Data and AI Services\" }\n ,\"microsoft.timeseriesinsights/environments\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environment\" }\n ,\"microsoft.timeseriesinsights/environments/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments access policy\" }\n ,\"microsoft.timeseriesinsights/environments/eventsources\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments event source\" }\n ,\"microsoft.timeseriesinsights/environments/referencedatasets\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments reference data set\" }\n ,\"microsoft.toolchainorchestrator/activations\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator activation\" }\n ,\"microsoft.toolchainorchestrator/campaigns\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaign\" }\n ,\"microsoft.toolchainorchestrator/campaigns/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaigns version\" }\n ,\"microsoft.toolchainorchestrator/catalogs\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalog\" }\n ,\"microsoft.toolchainorchestrator/catalogs/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalogs version\" }\n ,\"microsoft.toolchainorchestrator/diagnostics\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator diagnostic\" }\n ,\"microsoft.toolchainorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instance\" }\n ,\"microsoft.toolchainorchestrator/instances/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instances version\" }\n ,\"microsoft.toolchainorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solution\" }\n ,\"microsoft.toolchainorchestrator/solutions/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solutions version\" }\n ,\"microsoft.toolchainorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator target\" }\n ,\"microsoft.toolchainorchestrator/targets/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator targets version\" }\n ,\"microsoft.updatemanager/updaterules\": { \"SingularDisplayName\": \"Update Rule\" }\n ,\"microsoft.usagebilling/accounts\": { \"SingularDisplayName\": \"Microsoft.UsageBilling account\" }\n ,\"microsoft.usagebilling/accounts/dataexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts data export\" }\n ,\"microsoft.usagebilling/accounts/inputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts input\" }\n ,\"microsoft.usagebilling/accounts/metricexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts metric export\" }\n ,\"microsoft.usagebilling/accounts/pav2outputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pav2output\" }\n ,\"microsoft.usagebilling/accounts/pipelines\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipeline\" }\n ,\"microsoft.usagebilling/accounts/pipelines/outputselectors\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipelines output selector\" }\n ,\"microsoft.verifiedid/authorities\": { \"SingularDisplayName\": \"Microsoft.VerifiedId authority\" }\n ,\"microsoft.videoindexer/accounts\": { \"SingularDisplayName\": \"Azure AI Video Indexer\" }\n ,\"microsoft.virtualmachineimages/imagetemplates\": { \"SingularDisplayName\": \"Image template\" }\n ,\"microsoft.visualstudio/account\": { \"SingularDisplayName\": \"Azure DevOps organization\" }\n ,\"microsoft.vmware/resourcepools\": { \"SingularDisplayName\": \"Microsoft.VMware resource pool\" }\n ,\"microsoft.vmware/vcenters\": { \"SingularDisplayName\": \"Microsoft.VMware vcenter\" }\n ,\"microsoft.vmware/vcenters/inventoryitems\": { \"SingularDisplayName\": \"Microsoft.VMware vcenters inventory item\" }\n ,\"microsoft.vmware/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine\" }\n ,\"microsoft.vmware/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine template\" }\n ,\"microsoft.vmware/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.VMware virtual network\" }\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudnodes\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud node\" }\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudservices\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud service\" }\n ,\"microsoft.vmwarecloudsimple/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple virtual machine\" }\n ,\"microsoft.vnfmanager/devices\": { \"SingularDisplayName\": \"Microsoft.VnfManager device\" }\n ,\"microsoft.vnfmanager/vendors\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendor\" }\n ,\"microsoft.vnfmanager/vendors/skus\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendors SKU\" }\n ,\"microsoft.vnfmanager/vnfs\": { \"SingularDisplayName\": \"Microsoft.VnfManager vnf\" }\n ,\"microsoft.voiceservices/communicationsgateways\": { \"SingularDisplayName\": \"Communications Gateway\" }\n ,\"microsoft.voiceservices/communicationsgateways/testlines\": { \"SingularDisplayName\": \"Communications Gateway Test Line\" }\n ,\"microsoft.vsonline/accounts\": { \"SingularDisplayName\": \"Microsoft.VSOnline account\" }\n ,\"microsoft.vsonline/plans\": { \"SingularDisplayName\": \"Visual Studio Online Plan\" }\n ,\"microsoft.web/certificates\": { \"SingularDisplayName\": \"Microsoft.Web certificate\" }\n ,\"microsoft.web/connectiongateways\": { \"SingularDisplayName\": \"App Service on-premises data gateway\" }\n ,\"microsoft.web/connections\": { \"SingularDisplayName\": \"App Service API connection\" }\n ,\"microsoft.web/containerapps\": { \"SingularDisplayName\": \"Microsoft.Web container app\" }\n ,\"microsoft.web/containerapps/revisions\": { \"SingularDisplayName\": \"Microsoft.Web container apps revision\" }\n ,\"microsoft.web/customapis\": { \"SingularDisplayName\": \"Logic apps custom connector\" }\n ,\"microsoft.web/deletedsites\": { \"SingularDisplayName\": \"Microsoft.Web deleted site\" }\n ,\"microsoft.web/hostingenvironments\": { \"SingularDisplayName\": \"App Service Environment\" }\n ,\"microsoft.web/ishostingenvironmentnameavailable\": { \"SingularDisplayName\": \"Microsoft.Web ishostingenvironmentnameavailable\" }\n ,\"microsoft.web/kubeenvironments\": { \"SingularDisplayName\": \"App Service Kubernetes Environment\" }\n ,\"microsoft.web/logicappstemplate\": { \"SingularDisplayName\": \"Logic Apps Template\" }\n ,\"microsoft.web/publishingusers\": { \"SingularDisplayName\": \"Microsoft.Web publishing user\" }\n ,\"microsoft.web/serverfarms\": { \"SingularDisplayName\": \"App Service plan\" }\n ,\"microsoft.web/sites\": { \"SingularDisplayName\": \"App Service web app\" }\n ,\"microsoft.web/sites/slots\": { \"SingularDisplayName\": \"App Service deployment slot\" }\n ,\"microsoft.web/sourcecontrols\": { \"SingularDisplayName\": \"Microsoft.Web sourcecontrol\" }\n ,\"microsoft.web/staticsites\": { \"SingularDisplayName\": \"Static Web App\" }\n ,\"microsoft.weightsandbiases/instances\": { \"SingularDisplayName\": \"Azure Native Weights & Biases Cloud Service\" }\n ,\"microsoft.whiteboxcadlprovider/whiteboxresources\": { \"SingularDisplayName\": \"Microsoft.WhiteBoxCadlProvider white box resource\" }\n ,\"microsoft.windows365/cloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.Windows365 cloud pc delegated msi\" }\n ,\"microsoft.windowsesu/multipleactivationkeys\": { \"SingularDisplayName\": \"Microsoft.WindowsESU multiple activation key\" }\n ,\"microsoft.windowsiot/deviceservices\": { \"SingularDisplayName\": \"Microsoft.WindowsIoT device service\" }\n ,\"microsoft.windowspushnotificationservices/registrations\": { \"SingularDisplayName\": \"Windows Push Notification Service\" }\n ,\"microsoft.workloadmonitor/monitors\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitor\" }\n ,\"microsoft.workloadmonitor/monitors/history\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitors history\" }\n ,\"microsoft.workloads/configurationvalidationresults\": { \"SingularDisplayName\": \"Microsoft.Workloads configuration validation result\" }\n ,\"microsoft.workloads/connectors\": { \"SingularDisplayName\": \"Microsoft.Workloads connector\" }\n ,\"microsoft.workloads/connectors/acssbackups\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors acss backup\" }\n ,\"microsoft.workloads/connectors/amsinsights\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors ams insight\" }\n ,\"microsoft.workloads/connectors/sapvirtualinstancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors sap virtual instance monitor\" }\n ,\"microsoft.workloads/epicvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for Epic solution\" }\n ,\"microsoft.workloads/insights\": { \"SingularDisplayName\": \"Microsoft.Workloads insight\" }\n ,\"microsoft.workloads/instancegroupmonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance group monitor\" }\n ,\"microsoft.workloads/instancehealthdefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definition\" }\n ,\"microsoft.workloads/instancehealthdefinitions/signaldefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definitions signal definition\" }\n ,\"microsoft.workloads/instancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance monitor\" }\n ,\"microsoft.workloads/monitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP solutions\" }\n ,\"microsoft.workloads/oraclevirtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instance\" }\n ,\"microsoft.workloads/oraclevirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instances database instance\" }\n ,\"microsoft.workloads/phpworkloads\": { \"SingularDisplayName\": \"Microsoft.Workloads php workload\" }\n ,\"microsoft.workloads/phpworkloads/wordpressinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads php workloads wordpress instance\" }\n ,\"microsoft.workloads/sapdiscoverysites\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery site\" }\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instance\" }\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances/serverinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instances server instance\" }\n ,\"microsoft.workloads/sapvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/applicationinstances\": { \"SingularDisplayName\": \"App server instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/centralinstances\": { \"SingularDisplayName\": \"Central service instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Database for SAP solutions\" }\n ,\"microsoft.workloads/virtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instance\" }\n ,\"microsoft.workloads/virtualinstances/components\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instances component\" }\n ,\"microsoft.workloads/workloadinstance\": { \"SingularDisplayName\": \"My Resource\" }\n ,\"microsoft.zerotrustsegmentation/segmentationmanagers\": { \"SingularDisplayName\": \"Segmentation Manager\" }\n ,\"mongodb.atlas/organizations\": { \"SingularDisplayName\": \"MongoDB Atlas Organization\" }\n ,\"neon.postgres/organizations\": { \"SingularDisplayName\": \"Neon Serverless Postgres Organization\" }\n ,\"newrelic.observability/monitors\": { \"SingularDisplayName\": \"New Relic\" }\n ,\"nginx.nginxplus/nginxdeployments\": { \"SingularDisplayName\": \"NGINXaaS\" }\n ,\"oracle.database/autonomousdatabases\": { \"SingularDisplayName\": \"Autonomous Database\" }\n ,\"oracle.database/basedb\": { \"SingularDisplayName\": \"Autonomous Database\" }\n ,\"oracle.database/cloudexadatainfrastructures\": { \"SingularDisplayName\": \"Oracle Exadata Infrastructure\" }\n ,\"oracle.database/cloudvmclusters\": { \"SingularDisplayName\": \"Oracle Exadata VM Cluster\" }\n ,\"oracle.database/exadbvmclusters\": { \"SingularDisplayName\": \"Oracle Exascale VM Cluster\" }\n ,\"oracle.database/exascaledbstoragevaults\": { \"SingularDisplayName\": \"Oracle Exascale DB Storage Vault\" }\n ,\"oracle.database/networkanchors\": { \"SingularDisplayName\": \"Network Anchor\" }\n ,\"oracle.database/oraclesubscriptions\": { \"SingularDisplayName\": \"OracleSubscription\" }\n ,\"oracle.database/resourceanchors\": { \"SingularDisplayName\": \"Resource Anchor\" }\n ,\"paloaltonetworks.cloudngfw/firewalls\": { \"SingularDisplayName\": \"Cloud NGFW by Palo Alto Networks\" }\n ,\"paloaltonetworks.cloudngfw/globalrulestacks\": { \"SingularDisplayName\": \"Global Rulestack\" }\n ,\"paloaltonetworks.cloudngfw/localrulestacks\": { \"SingularDisplayName\": \"Local Rulestack for Cloud NGFW by Palo Alto Networks\" }\n ,\"pinecone.vectordb/organizations\": { \"SingularDisplayName\": \"Azure Native Pinecone Cloud Service\" }\n ,\"purestorage.block/reservations\": { \"SingularDisplayName\": \"Azure Native Pure Storage Cloud Service\" }\n ,\"purestorage.block/storagepools\": { \"SingularDisplayName\": \"Storage pool\" }\n ,\"purestorage.block/storagepools/avsstoragecontainers\": { \"SingularDisplayName\": \"PureStorage.Block storage pools avs storage container\" }\n })[tolower(id)]\n}\n", + "$fxv#4": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_5(id: string) {\n dynamic({\n \"qumulo.qaas/storages\": { \"SingularDisplayName\": \"Qumulo.QaaS storage\" }\n ,\"qumulo.storage/filesystems\": { \"SingularDisplayName\": \"Azure Native Qumulo Scalable File Service\" }\n ,\"solarwinds.observability/organizations\": { \"SingularDisplayName\": \"SolarWinds Observability\" }\n ,\"splitio.experimentation/experimentationworkspaces\": { \"SingularDisplayName\": \"Split Experimentation Workspace\" }\n ,\"wandisco.fusion/migrators\": { \"SingularDisplayName\": \"LiveData Migrator\" }\n ,\"wandisco.fusion/migrators/datatransferagents\": { \"SingularDisplayName\": \"Data Transfer Agent\" }\n ,\"wandisco.fusion/migrators/exclusiontemplates\": { \"SingularDisplayName\": \"Exclusion\" }\n ,\"wandisco.fusion/migrators/livedatamigrations\": { \"SingularDisplayName\": \"Migration\" }\n ,\"wandisco.fusion/migrators/metadatamigrations\": { \"SingularDisplayName\": \"Metadata Migration\" }\n ,\"wandisco.fusion/migrators/metadatatargets\": { \"SingularDisplayName\": \"Metadata Target\" }\n ,\"wandisco.fusion/migrators/pathmappings\": { \"SingularDisplayName\": \"Path Mapping\" }\n ,\"wandisco.fusion/migrators/targets\": { \"SingularDisplayName\": \"Target\" }\n ,\"wandisco.fusion/migrators/verifications\": { \"SingularDisplayName\": \"Verification\" }\n })[tolower(id)]\n}\n", + "$fxv#5": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// resource_type\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData')\nresource_type(id: string) {\n coalesce(_resource_type_1(id), _resource_type_2(id), _resource_type_3(id), _resource_type_4(id), _resource_type_5(id))\n}\n", + "$fxv#6": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Common utility functions\n//\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\n//======================================================================================================================\n\n\n//===| Date functions |=================================================================================================\n\n// monthstring\n.create-or-alter function \nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \nmonthstring(['date']: datetime, length: int = 9)\n{\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\n}\n\n// datestring\n.create-or-alter function \nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n let month = (d: datetime) { monthstring(d, 3) };\n let endDate = iff(end == datetime('0001-01-01'), start, end);\n let sameDate = startofday(start) == startofday(endDate);\n let sameMonth = startofmonth(start) == startofmonth(endDate);\n let sameYear = startofyear(start) == startofyear(endDate);\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\n let currentYear = sameYear and startofyear(start) == startofyear(now());\n case(\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\n fullYear,\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\n // 1 full mo, same year | Mmm yyyy\n fullMonth and sameMonth and sameYear,\n strcat(month(start), ' ', getyear(start)),\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\n fullMonth and sameYear,\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\n fullMonth and not(sameYear),\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\n sameDate,\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\n not(fullMonth) and sameMonth and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\n not(fullMonth) and not(sameMonth) and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\n )\n}\n\n// daterange\n.create-or-alter function \nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n datestring(start, end)\n}\n\n// monthsago\n.create-or-alter function \nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\nmonthsago(months: int)\n{\n datetime_add('month', -months, startofmonth(now()))\n}\n\n\n//===| Number functions |===============================================================================================\n// NOTE: Must be defined before string converters\n\n// delta\n.create-or-alter function \nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \ndelta(oldval: double, newval: double)\n{\n (newval - todouble(oldval))/oldval\n}\n\n// percentOfTotal\n// NOTE: Must be before percent() function\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercentOfTotal(t: (Count: long), tot: long)\n{\n let total = todouble(tot);\n t \n | extend Percent = round(Count / total * 100, 3) \n | order by Count desc\n}\n\n// percent\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercent(t: (Count: long))\n{\n let total = todouble(toscalar(t | summarize sum(Count)));\n percentOfTotal(t, total)\n}\n\n// plusminus\n.create-or-alter function \nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\nplusminus(val: string)\n{\n let neg = substring(val, 0, 1) == '-';\n iff(neg, val, strcat('+', val))\n}\n\n// updown\n.create-or-alter function \nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\nupdown(val: string)\n{\n // TODO: Handle 0\n let neg = substring(val, 0, 1) == '-';\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\n}\n\n\n//===| String functions |===============================================================================================\n\n// percentstring\n// NOTE: Must be defined before deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\npercentstring(num: double, total: double = 1.0, places: int = 9)\n{\n let value = 1.0 * num / total * 100;\n strcat(case(\n places != 9, round(value, places),\n value < 10, round(value, 2),\n round(value, 1)\n ), '%')\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// arraystring\n.create-or-alter function \nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\narraystring(arr: dynamic)\n{\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\n tostring(arr)\n , @'^\\[\"', '')\n , @'\"\\]$', '')\n , @'^, ', '')\n , @', $', '')\n , @'^\\[]$', '')\n , '\",\"', ', ')\n}\n\n// deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\n{\n let d = delta(oldval, newval);\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\n}\n\n// diffstring\n.create-or-alter function \nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\ndiffstring(oldval: double, newval: double, places: int = 1)\n{\n plusminus(round(newval - oldval, places))\n}\n\n// numberstring\n.create-or-alter function \nwith (docstring = 'Convert a number to a string', folder = 'Common')\nnumberstring(num: double, abbrev: bool = true)\n{\n replace_regex(case(\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\n tostring(num)\n ), @'\\.0$', '')\n}\n\n\n//===| Other |==========================================================================================================\n\n// ifempty\n.create-or-alter function \nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\nifempty(val: dynamic, defaultVal: dynamic)\n{\n iff(isempty(val), defaultVal, val)\n}\n", + "$fxv#7": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Settings |=======================================================================================================\n\n.create-merge table HubSettingsLog (\n version: string,\n scopes: dynamic,\n retention: dynamic\n)\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// HubSettings function\n.create-or-alter function\nwith (docstring='Gets the latest version of hub settings.', folder='Settings')\nHubSettings()\n{\n HubSettingsLog\n | extend timestamp = ingestion_time()\n | summarize arg_max(timestamp, *)\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// HubScopes function\n.create-or-alter function\nwith (docstring='Gets the currently configured scopes.', folder='Settings')\nHubScopes()\n{\n HubSettings\n | project scopes\n | mv-expand scopes\n}\n\n\n//===| Open data |======================================================================================================\n\n// PricingUnits -- Create table if it doesn't exist\n.create-merge table PricingUnits ( ignore: string )\n\n// PricingUnits -- Remove all columns\n.alter table PricingUnits ( ignore: string )\n\n// PricingUnits -- Redefine all columns to change types\n.alter table PricingUnits (\n x_PricingUnitDescription: string,\n x_PricingBlockSize: real,\n PricingUnit: string\n)\n\n// Regions\n.create-merge table Regions(\n ResourceLocation: string,\n RegionId: string,\n RegionName: string\n)\n\n// ResourceTypes\n.create-merge table ResourceTypes(\n x_ResourceType: string,\n SingularDisplayName: string,\n PluralDisplayName: string,\n LowerSingularDisplayName: string,\n LowerPluralDisplayName: string,\n IsPreview: bool,\n Description: string,\n IconUri: string\n)\n\n// Services\n.create-merge table Services(\n x_ConsumedService: string,\n x_ResourceType: string,\n ServiceName: string,\n ServiceCategory: string,\n ServiceSubcategory: string,\n PublisherName: string,\n x_PublisherCategory: string,\n x_Environment: string,\n x_ServiceModel: string\n)\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// parse_resourceid\n.create-or-alter function\nwith (docstring = 'Parses an Azure resource ID to extract resource attributes like the name, type, resource group, and subaccount ID.', folder = 'Common')\nparse_resourceid(resourceId: string) {\n let ResourceId = tolower(resourceId);\n // let ResourceId = tolower('/providers/Microsoft.BillingBenefits/savingsPlanOrders/2d2e284b-0638-427e-b8c6-1b874d4f17c8/sp/xxx');\n let SubAccountId = tostring(extract('/subscriptions/[^/]+', 1, ResourceId));\n let x_ResourceGroupName = tostring(extract('/resourcegroups/[^/]+', 1, ResourceId));\n let providerPath = iff(ResourceId !contains '/providers/', '', split(iff(ResourceId startswith '/subscriptions/', strcat('/providers/microsoft.resources/', ResourceId), ResourceId), '/providers/')[-1]);\n let x_ResourceProvider = iff(isempty(providerPath), '', split(providerPath, '/')[0]);\n let tmp_ResourceProviderPath = iff(isempty(providerPath), '', substring(providerPath, strlen(x_ResourceProvider) + 1));\n let segments = split(tmp_ResourceProviderPath, '/');\n let ResourceName = trim(@'/+', replace_string(strcat_array(array_iff(\n dynamic([false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true]),\n segments, dynamic([])), '/'), '//', '/'));\n let x_ResourceTypePath = trim(@'/+', replace_string(strcat_array(array_iff(\n dynamic([true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]),\n segments, dynamic([])), '/'), '//', '/'));\n let xRT = iff(isempty(x_ResourceProvider) or isempty(x_ResourceTypePath), '', strcat(x_ResourceProvider, '/', x_ResourceTypePath));\n // TODO: Remove ResourceType in 0.9\n bag_pack('ResourceId', ResourceId, 'ResourceName', ResourceName, 'ResourceType', xRT, 'SubAccountId', SubAccountId, 'x_ResourceGroupName', x_ResourceGroupName, 'x_ResourceProvider', x_ResourceProvider, 'x_ResourceType', xRT)\n}\n", + "$fxv#8": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| ActualCosts |====================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_raw table -- Create the table if it doesn't exist\n.create-merge table ActualCosts_raw ( ignore: string )\n\n// ActualCosts_raw table -- Remove all columns to allow changing column types\n.alter table ActualCosts_raw ( ignore: string )\n\n// ActualCosts_raw table -- Redefine all columns\n.alter table ActualCosts_raw (\n AccountName: string,\n AccountOwnerId: string,\n AdditionalInfo: string,\n AvailabilityZone: string,\n BillingAccountId: string, \n BillingAccountName: string,\n BillingCurrency: string,\n BillingPeriodEndDate: datetime,\n BillingPeriodStartDate: datetime,\n BillingProfileId: string,\n BillingProfileName: string,\n ChargeType: string,\n ConsumedService: string,\n CostCenter: string,\n Cost: real,\n Date: datetime,\n EffectivePrice: real,\n Frequency: string,\n InvoiceSection: string,\n InvoiceSectionId: string,\n IsAzureCreditEligible: bool,\n MeterCategory: string,\n MeterId: string,\n MeterName: string,\n MeterRegion: string,\n MeterSubCategory: string,\n OfferId: string,\n PartNumber: string,\n PlanName: string,\n Product: string,\n ProductOrderId: string,\n ProductOrderName: string,\n PublisherName: string,\n PublisherType: string,\n Quantity: real,\n ReservationId: string,\n ReservationName: string,\n ResourceGroup: string,\n ResourceId: string,\n ResourceLocation: string,\n ResourceName: string,\n ServiceFamily: string,\n ServiceInfo1: string,\n ServiceInfo2: string,\n SubscriptionId: string,\n SubscriptionName: string,\n Tags: string,\n Term: string,\n UnitOfMeasure: string,\n UnitPrice: real\n)\n\n// ActualCosts_raw ingestion mapping\n.create-or-alter table ActualCosts_raw ingestion parquet mapping \"ActualCosts_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\n]\n```\n\n// ActualCosts_raw retention policy (clear historical data)\n.alter-merge table ActualCosts_raw policy retention softdelete = 0d recoverability = disabled\n\n// ActualCosts_raw retention policy (set the user-defined retention period)\n.alter-merge table ActualCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable ActualCosts_raw streaming ingestion (required for Fabric)\n.alter table ActualCosts_raw policy streamingingestion disable\n\n\n//===| AmortizedCosts |=================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_raw table -- Create the table if it doesn't exist\n.create-merge table AmortizedCosts_raw ( ignore: string )\n\n// AmortizedCosts_raw table -- Remove all columns to allow changing column types\n.alter table AmortizedCosts_raw ( ignore: string )\n\n// AmortizedCosts_raw table -- Redefine all columns\n.alter table AmortizedCosts_raw (\n AccountName: string,\n AccountOwnerId: string,\n AdditionalInfo: string,\n AvailabilityZone: string,\n BillingAccountId: string, \n BillingAccountName: string,\n BillingCurrency: string,\n BillingPeriodEndDate: datetime,\n BillingPeriodStartDate: datetime,\n BillingProfileId: string,\n BillingProfileName: string,\n ChargeType: string,\n ConsumedService: string,\n CostCenter: string,\n Cost: real,\n Date: datetime,\n EffectivePrice: real,\n Frequency: string,\n InvoiceSection: string,\n InvoiceSectionId: string,\n IsAzureCreditEligible: bool,\n MeterCategory: string,\n MeterId: string,\n MeterName: string,\n MeterRegion: string,\n MeterSubCategory: string,\n OfferId: string,\n PartNumber: string,\n PlanName: string,\n Product: string,\n ProductOrderId: string,\n ProductOrderName: string,\n PublisherName: string,\n PublisherType: string,\n Quantity: real,\n ReservationId: string,\n ReservationName: string,\n ResourceGroup: string,\n ResourceId: string,\n ResourceLocation: string,\n ResourceName: string,\n ServiceFamily: string,\n ServiceInfo1: string,\n ServiceInfo2: string,\n SubscriptionId: string,\n SubscriptionName: string,\n Tags: string,\n Term: string,\n UnitOfMeasure: string,\n UnitPrice: real\n)\n\n// AmortizedCosts_raw ingestion mapping\n.create-or-alter table AmortizedCosts_raw ingestion parquet mapping \"AmortizedCosts_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\n]\n```\n\n// AmortizedCosts_raw retention policy (clear historical data)\n.alter-merge table AmortizedCosts_raw policy retention softdelete = 0d recoverability = disabled\n\n// AmortizedCosts_raw retention policy (set the user-defined retention period)\n.alter-merge table AmortizedCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable AmortizedCosts_raw streaming ingestion (required for Fabric)\n.alter table AmortizedCosts_raw policy streamingingestion disable\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_raw table -- Create the table if it doesn't exist\n.create-merge table CommitmentDiscountUsage_raw ( ignore: string )\n\n// CommitmentDiscountUsage_raw table -- Remove all columns to allow changing column types\n.alter table CommitmentDiscountUsage_raw ( ignore: string )\n\n// CommitmentDiscountUsage_raw table -- Redefine all columns\n.alter table CommitmentDiscountUsage_raw (\n InstanceFlexibilityGroup: string,\n InstanceFlexibilityRatio: real,\n InstanceId: string,\n Kind: string,\n ReservationId: string,\n ReservationOrderId: string,\n ReservedHours: real,\n SkuName: string,\n TotalReservedQuantity: real,\n UsageDate: datetime,\n UsedHours: real,\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// CommitmentDiscountUsage_raw ingestion mapping\n.create-or-alter table CommitmentDiscountUsage_raw ingestion parquet mapping \"CommitmentDiscountUsage_raw_mapping\"\n```\n[\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\n { \"Column\": \"InstanceId\", \"Properties\": { \"Field\": \"InstanceId\" } },\n { \"Column\": \"Kind\", \"Properties\": { \"Field\": \"Kind\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\n { \"Column\": \"ReservedHours\", \"Properties\": { \"Field\": \"ReservedHours\" } },\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\n { \"Column\": \"TotalReservedQuantity\", \"Properties\": { \"Field\": \"TotalReservedQuantity\" } },\n { \"Column\": \"UsageDate\", \"Properties\": { \"Field\": \"UsageDate\" } },\n { \"Column\": \"UsedHours\", \"Properties\": { \"Field\": \"UsedHours\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// CommitmentDiscountUsage_raw retention policy (clear historical data)\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = 0d recoverability = disabled\n\n// CommitmentDiscountUsage_raw retention policy (set the user-defined retention period)\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable CommitmentDiscountUsage_raw streaming ingestion (required for Fabric)\n.alter table CommitmentDiscountUsage_raw policy streamingingestion disable\n\n\n//===| Costs |==========================================================================================================\n// Supported versions:\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n// - Tencent: 1.0 -- See https://www.tencentcloud.com/document/product/555/67495 / https://www.tencentcloud.com/document/product/555/67496\n// - Alibaba: 1.0 -- See https://www.alibabacloud.com/help/en/user-center/user-guide/export-alibaba-cloud-standard-billing-focus\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_raw table -- Create the table if it doesn't exist\n.create-merge table Costs_raw ( ignore: string )\n\n// Costs_raw table -- Remove all columns to allow changing column types\n.alter table Costs_raw ( ignore: string )\n\n// Costs_raw table -- Redefine all columns\n.alter table Costs_raw (\n AvailabilityZone: string, // FOCUS 0.5+\n BilledCost: real, // FOCUS 0.5+\n BillingAccountId: string, // FOCUS 0.5+\n BillingAccountName: string, // FOCUS 0.5+\n BillingAccountType: string, // Azure 1.0-preview(v1)+\n BillingCurrency: string, // FOCUS 0.5+\n BillingPeriodEnd: datetime, // FOCUS 0.5+\n BillingPeriodStart: datetime, // FOCUS 0.5+\n CapacityReservationId: string, // FOCUS 1.1+\n CapacityReservationStatus: string, // FOCUS 1.1+\n ChargeCategory: string, // FOCUS 1.0-preview+\n ChargeClass: string, // FOCUS 1.0+\n ChargeDescription: string, // FOCUS 1.0+\n ChargeFrequency: string, // FOCUS 1.0+\n ChargePeriodEnd: datetime, // FOCUS 0.5+\n ChargePeriodStart: datetime, // FOCUS 0.5+\n ChargeSubcategory: string, // FOCUS 1.0-preview only\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview+\n CommitmentDiscountId: string, // FOCUS 1.0-preview+\n CommitmentDiscountName: string, // FOCUS 1.0-preview+\n CommitmentDiscountQuantity: real, // FOCUS 1.1+\n CommitmentDiscountStatus: string, // FOCUS 1.0+\n CommitmentDiscountType: string, // FOCUS 1.0-preview+\n CommitmentDiscountUnit: string, // FOCUS 1.1+\n ConsumedQuantity: real, // FOCUS 1.0+\n ConsumedUnit: string, // FOCUS 1.0+\n ContractedCost: real, // FOCUS 1.0+\n ContractedUnitPrice: real, // FOCUS 1.0+\n EffectiveCost: real, // FOCUS 1.0-preview+\n InvoiceId: string, // FOCUS 1.2+\n InvoiceIssuerName: string, // FOCUS 0.5+\n ListCost: real, // FOCUS 1.0-preview+\n ListUnitPrice: real, // FOCUS 1.0-preview+\n PricingCategory: string, // FOCUS 1.0-preview+\n PricingCurrency: string, // FOCUS 1.2+\n PricingQuantity: real, // FOCUS 1.0-preview+\n PricingUnit: string, // FOCUS 1.0-preview+\n ProviderName: string, // FOCUS 0.5+\n PublisherName: string, // FOCUS 0.5+\n Region: string, // FOCUS 0.5-1.0-preview (deprecated)\n RegionId: string, // FOCUS 1.0+\n RegionName: string, // FOCUS 1.0+\n ResourceId: string, // FOCUS 0.5+\n ResourceName: string, // FOCUS 0.5+\n ResourceType: string, // FOCUS 1.0-preview+\n ServiceCategory: string, // FOCUS 0.5+\n ServiceName: string, // FOCUS 0.5+\n ServiceSubcategory: string, // FOCUS 1.1+\n SkuId: string, // FOCUS 1.0-preview+\n SkuMeter: string, // FOCUS 1.1+\n SkuPriceDetails: string, // FOCUS 1.1+\n SkuPriceId: string, // FOCUS 1.0-preview+\n SubAccountId: string, // FOCUS 0.5+\n SubAccountName: string, // FOCUS 0.5+\n SubAccountType: string, // Azure 1.0-preview(v1)+\n Tags: string, // FOCUS 1.0-preview+\n UsageAmount: real, // GCP Jan 2024 -- Removed Mar 2024 (UsageQuantity)\n UsageQuantity: real, // FOCUS 1.0-preview only\n UsageUnit: string, // FOCUS 1.0-preview only\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_AmortizationClass: string, // Azure 1.2-preview+\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingItemCode: string, // Alibaba 1.0+\n x_BillingItemName: string, // Alibaba 1.0+\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_CommodityCode: string, // Alibaba 1.0+\n x_CommodityName: string, // Alibaba 1.0+\n x_ComponentName: string, // Tencent 1.0+\n x_ComponentType: string, // Tencent 1.0+\n x_ContractedCostInUsd: real, // Azure 1.0+\n x_Cost: real, // GCP Jan 2024 -- Removed Jun 2024 (ContractedCost)\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: string, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_CostType: string, // GCP Jan 2024\n x_Credits: string, // GCP Jan 2024\n x_CurrencyConversionRate: real, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: string, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0+\n x_InstanceID: string, // Alibaba 1.0+\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_OnDemandCost: real, // Azure 1.0-preview(v1) only\n x_OnDemandCostInUsd: real, // Azure 1.0-preview(v1) only\n x_OnDemandUnitPrice: real, // Azure 1.0-preview(v1) only\n x_Operation: string, // AWS 1.0\n x_OwnerAccountID: string, // Tencent 1.0+\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\n x_PricingCurrency: string, // Azure 1.0-preview(v1)-1.0r2\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServiceModel: string, // Azure 1.2-preview+\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: string, // Azure 1.0-preview(v1)-1.2-preview\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterName: string, // Azure 1.0-preview(v1)-1.0r2\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuPlanName: string, // Azure 1.2-preview+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string, // Hubs v1_0+\n x_SubproductName: string, // Tencent 1.0+ // cSpell:ignore Subproduct\n x_UsageType: string // AWS 1.0\n)\n\n// Costs_raw ingestion mapping\n.create-or-alter table Costs_raw ingestion parquet mapping \"Costs_raw_mapping\"\n```\n[\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BilledCost\", \"Properties\": { \"Field\": \"BilledCost\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingAccountType\", \"Properties\": { \"Field\": \"BillingAccountType\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEnd\", \"Properties\": { \"Field\": \"BillingPeriodEnd\" } },\n { \"Column\": \"BillingPeriodStart\", \"Properties\": { \"Field\": \"BillingPeriodStart\" } },\n { \"Column\": \"CapacityReservationId\", \"Properties\": { \"Field\": \"CapacityReservationId\" } },\n { \"Column\": \"CapacityReservationStatus\", \"Properties\": { \"Field\": \"CapacityReservationStatus\" } },\n { \"Column\": \"ChargeCategory\", \"Properties\": { \"Field\": \"ChargeCategory\" } },\n { \"Column\": \"ChargeClass\", \"Properties\": { \"Field\": \"ChargeClass\" } },\n { \"Column\": \"ChargeDescription\", \"Properties\": { \"Field\": \"ChargeDescription\" } },\n { \"Column\": \"ChargeFrequency\", \"Properties\": { \"Field\": \"ChargeFrequency\" } },\n { \"Column\": \"ChargePeriodEnd\", \"Properties\": { \"Field\": \"ChargePeriodEnd\" } },\n { \"Column\": \"ChargePeriodStart\", \"Properties\": { \"Field\": \"ChargePeriodStart\" } },\n { \"Column\": \"ChargeSubcategory\", \"Properties\": { \"Field\": \"ChargeSubcategory\" } },\n { \"Column\": \"CommitmentDiscountCategory\", \"Properties\": { \"Field\": \"CommitmentDiscountCategory\" } },\n { \"Column\": \"CommitmentDiscountId\", \"Properties\": { \"Field\": \"CommitmentDiscountId\" } },\n { \"Column\": \"CommitmentDiscountName\", \"Properties\": { \"Field\": \"CommitmentDiscountName\" } },\n { \"Column\": \"CommitmentDiscountQuantity\", \"Properties\": { \"Field\": \"CommitmentDiscountQuantity\" } },\n { \"Column\": \"CommitmentDiscountStatus\", \"Properties\": { \"Field\": \"CommitmentDiscountStatus\" } },\n { \"Column\": \"CommitmentDiscountType\", \"Properties\": { \"Field\": \"CommitmentDiscountType\" } },\n { \"Column\": \"CommitmentDiscountUnit\", \"Properties\": { \"Field\": \"CommitmentDiscountUnit\" } },\n { \"Column\": \"ConsumedQuantity\", \"Properties\": { \"Field\": \"ConsumedQuantity\" } },\n { \"Column\": \"ConsumedUnit\", \"Properties\": { \"Field\": \"ConsumedUnit\" } },\n { \"Column\": \"ContractedCost\", \"Properties\": { \"Field\": \"ContractedCost\" } },\n { \"Column\": \"ContractedUnitPrice\", \"Properties\": { \"Field\": \"ContractedUnitPrice\" } },\n { \"Column\": \"EffectiveCost\", \"Properties\": { \"Field\": \"EffectiveCost\" } },\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\n { \"Column\": \"InvoiceIssuerName\", \"Properties\": { \"Field\": \"InvoiceIssuerName\" } },\n { \"Column\": \"ListCost\", \"Properties\": { \"Field\": \"ListCost\" } },\n { \"Column\": \"ListUnitPrice\", \"Properties\": { \"Field\": \"ListUnitPrice\" } },\n { \"Column\": \"PricingCategory\", \"Properties\": { \"Field\": \"PricingCategory\" } },\n { \"Column\": \"PricingCurrency\", \"Properties\": { \"Field\": \"PricingCurrency\" } },\n { \"Column\": \"PricingQuantity\", \"Properties\": { \"Field\": \"PricingQuantity\" } },\n { \"Column\": \"PricingUnit\", \"Properties\": { \"Field\": \"PricingUnit\" } },\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\n { \"Column\": \"RegionId\", \"Properties\": { \"Field\": \"RegionId\" } },\n { \"Column\": \"RegionName\", \"Properties\": { \"Field\": \"RegionName\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\n { \"Column\": \"ServiceCategory\", \"Properties\": { \"Field\": \"ServiceCategory\" } },\n { \"Column\": \"ServiceName\", \"Properties\": { \"Field\": \"ServiceName\" } },\n { \"Column\": \"ServiceSubcategory\", \"Properties\": { \"Field\": \"ServiceSubcategory\" } },\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\n { \"Column\": \"SkuMeter\", \"Properties\": { \"Field\": \"SkuMeter\" } },\n { \"Column\": \"SkuPriceDetails\", \"Properties\": { \"Field\": \"SkuPriceDetails\" } },\n { \"Column\": \"SkuPriceId\", \"Properties\": { \"Field\": \"SkuPriceId\" } },\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\n { \"Column\": \"SubAccountType\", \"Properties\": { \"Field\": \"SubAccountType\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"UsageAmount\", \"Properties\": { \"Field\": \"UsageAmount\" } },\n { \"Column\": \"UsageQuantity\", \"Properties\": { \"Field\": \"UsageQuantity\" } },\n { \"Column\": \"UsageUnit\", \"Properties\": { \"Field\": \"UsageUnit\" } },\n { \"Column\": \"x_AccountId\", \"Properties\": { \"Field\": \"x_AccountId\" } },\n { \"Column\": \"x_AccountName\", \"Properties\": { \"Field\": \"x_AccountName\" } },\n { \"Column\": \"x_AccountOwnerId\", \"Properties\": { \"Field\": \"x_AccountOwnerId\" } },\n { \"Column\": \"x_AmortizationClass\", \"Properties\": { \"Field\": \"x_AmortizationClass\" } },\n { \"Column\": \"x_BilledCostInUsd\", \"Properties\": { \"Field\": \"x_BilledCostInUsd\" } },\n { \"Column\": \"x_BilledUnitPrice\", \"Properties\": { \"Field\": \"x_BilledUnitPrice\" } },\n { \"Column\": \"x_BillingAccountId\", \"Properties\": { \"Field\": \"x_BillingAccountId\" } },\n { \"Column\": \"x_BillingAccountName\", \"Properties\": { \"Field\": \"x_BillingAccountName\" } },\n { \"Column\": \"x_BillingExchangeRate\", \"Properties\": { \"Field\": \"x_BillingExchangeRate\" } },\n { \"Column\": \"x_BillingExchangeRateDate\", \"Properties\": { \"Field\": \"x_BillingExchangeRateDate\" } },\n { \"Column\": \"x_BillingItemCode\", \"Properties\": { \"Field\": \"x_BillingItemCode\" } },\n { \"Column\": \"x_BillingItemName\", \"Properties\": { \"Field\": \"x_BillingItemName\" } },\n { \"Column\": \"x_BillingProfileId\", \"Properties\": { \"Field\": \"x_BillingProfileId\" } },\n { \"Column\": \"x_BillingProfileName\", \"Properties\": { \"Field\": \"x_BillingProfileName\" } },\n { \"Column\": \"x_ChargeId\", \"Properties\": { \"Field\": \"x_ChargeId\" } },\n { \"Column\": \"x_ContractedCostInUsd\", \"Properties\": { \"Field\": \"x_ContractedCostInUsd\" } },\n { \"Column\": \"x_CommodityCode\", \"Properties\": { \"Field\": \"x_CommodityCode\" } },\n { \"Column\": \"x_CommodityName\", \"Properties\": { \"Field\": \"x_CommodityName\" } },\n { \"Column\": \"x_ComponentName\", \"Properties\": { \"Field\": \"x_ComponentName\" } },\n { \"Column\": \"x_ComponentType\", \"Properties\": { \"Field\": \"x_ComponentType\" } },\n { \"Column\": \"x_Cost\", \"Properties\": { \"Field\": \"x_Cost\" } },\n { \"Column\": \"x_CostAllocationRuleName\", \"Properties\": { \"Field\": \"x_CostAllocationRuleName\" } },\n { \"Column\": \"x_CostCategories\", \"Properties\": { \"Field\": \"x_CostCategories\" } },\n { \"Column\": \"x_CostCenter\", \"Properties\": { \"Field\": \"x_CostCenter\" } },\n { \"Column\": \"x_Credits\", \"Properties\": { \"Field\": \"x_Credits\" } },\n { \"Column\": \"x_CostType\", \"Properties\": { \"Field\": \"x_CostType\" } },\n { \"Column\": \"x_CurrencyConversionRate\", \"Properties\": { \"Field\": \"x_CurrencyConversionRate\" } },\n { \"Column\": \"x_CustomerId\", \"Properties\": { \"Field\": \"x_CustomerId\" } },\n { \"Column\": \"x_CustomerName\", \"Properties\": { \"Field\": \"x_CustomerName\" } },\n { \"Column\": \"x_Discount\", \"Properties\": { \"Field\": \"x_Discount\" } },\n { \"Column\": \"x_EffectiveCostInUsd\", \"Properties\": { \"Field\": \"x_EffectiveCostInUsd\" } },\n { \"Column\": \"x_EffectiveUnitPrice\", \"Properties\": { \"Field\": \"x_EffectiveUnitPrice\" } },\n { \"Column\": \"x_ExportTime\", \"Properties\": { \"Field\": \"x_ExportTime\" } },\n { \"Column\": \"x_InstanceID\", \"Properties\": { \"Field\": \"x_InstanceID\" } },\n { \"Column\": \"x_InvoiceId\", \"Properties\": { \"Field\": \"x_InvoiceId\" } },\n { \"Column\": \"x_InvoiceIssuerId\", \"Properties\": { \"Field\": \"x_InvoiceIssuerId\" } },\n { \"Column\": \"x_InvoiceSectionId\", \"Properties\": { \"Field\": \"x_InvoiceSectionId\" } },\n { \"Column\": \"x_InvoiceSectionName\", \"Properties\": { \"Field\": \"x_InvoiceSectionName\" } },\n { \"Column\": \"x_ListCostInUsd\", \"Properties\": { \"Field\": \"x_ListCostInUsd\" } },\n { \"Column\": \"x_Location\", \"Properties\": { \"Field\": \"x_Location\" } },\n { \"Column\": \"x_OnDemandCost\", \"Properties\": { \"Field\": \"x_OnDemandCost\" } },\n { \"Column\": \"x_OnDemandCostInUsd\", \"Properties\": { \"Field\": \"x_OnDemandCostInUsd\" } },\n { \"Column\": \"x_OnDemandUnitPrice\", \"Properties\": { \"Field\": \"x_OnDemandUnitPrice\" } },\n { \"Column\": \"x_Operation\", \"Properties\": { \"Field\": \"x_Operation\" } },\n { \"Column\": \"x_OwnerAccountID\", \"Properties\": { \"Field\": \"x_OwnerAccountID\" } },\n { \"Column\": \"x_PartnerCreditApplied\", \"Properties\": { \"Field\": \"x_PartnerCreditApplied\" } },\n { \"Column\": \"x_PartnerCreditRate\", \"Properties\": { \"Field\": \"x_PartnerCreditRate\" } },\n { \"Column\": \"x_PricingBlockSize\", \"Properties\": { \"Field\": \"x_PricingBlockSize\" } },\n { \"Column\": \"x_PricingCurrency\", \"Properties\": { \"Field\": \"x_PricingCurrency\" } },\n { \"Column\": \"x_PricingSubcategory\", \"Properties\": { \"Field\": \"x_PricingSubcategory\" } },\n { \"Column\": \"x_PricingUnitDescription\", \"Properties\": { \"Field\": \"x_PricingUnitDescription\" } },\n { \"Column\": \"x_Project\", \"Properties\": { \"Field\": \"x_Project\" } },\n { \"Column\": \"x_PublisherCategory\", \"Properties\": { \"Field\": \"x_PublisherCategory\" } },\n { \"Column\": \"x_PublisherId\", \"Properties\": { \"Field\": \"x_PublisherId\" } },\n { \"Column\": \"x_ResellerId\", \"Properties\": { \"Field\": \"x_ResellerId\" } },\n { \"Column\": \"x_ResellerName\", \"Properties\": { \"Field\": \"x_ResellerName\" } },\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\n { \"Column\": \"x_ResourceType\", \"Properties\": { \"Field\": \"x_ResourceType\" } },\n { \"Column\": \"x_ServiceCode\", \"Properties\": { \"Field\": \"x_ServiceCode\" } },\n { \"Column\": \"x_ServiceId\", \"Properties\": { \"Field\": \"x_ServiceId\" } },\n { \"Column\": \"x_ServiceModel\", \"Properties\": { \"Field\": \"x_ServiceModel\" } },\n { \"Column\": \"x_ServicePeriodEnd\", \"Properties\": { \"Field\": \"x_ServicePeriodEnd\" } },\n { \"Column\": \"x_ServicePeriodStart\", \"Properties\": { \"Field\": \"x_ServicePeriodStart\" } },\n { \"Column\": \"x_SkuDescription\", \"Properties\": { \"Field\": \"x_SkuDescription\" } },\n { \"Column\": \"x_SkuDetails\", \"Properties\": { \"Field\": \"x_SkuDetails\" } },\n { \"Column\": \"x_SkuIsCreditEligible\", \"Properties\": { \"Field\": \"x_SkuIsCreditEligible\" } },\n { \"Column\": \"x_SkuMeterCategory\", \"Properties\": { \"Field\": \"x_SkuMeterCategory\" } },\n { \"Column\": \"x_SkuMeterId\", \"Properties\": { \"Field\": \"x_SkuMeterId\" } },\n { \"Column\": \"x_SkuMeterName\", \"Properties\": { \"Field\": \"x_SkuMeterName\" } },\n { \"Column\": \"x_SkuMeterSubcategory\", \"Properties\": { \"Field\": \"x_SkuMeterSubcategory\" } },\n { \"Column\": \"x_SkuOfferId\", \"Properties\": { \"Field\": \"x_SkuOfferId\" } },\n { \"Column\": \"x_SkuOrderId\", \"Properties\": { \"Field\": \"x_SkuOrderId\" } },\n { \"Column\": \"x_SkuOrderName\", \"Properties\": { \"Field\": \"x_SkuOrderName\" } },\n { \"Column\": \"x_SkuPartNumber\", \"Properties\": { \"Field\": \"x_SkuPartNumber\" } },\n { \"Column\": \"x_SkuPlanName\", \"Properties\": { \"Field\": \"x_SkuPlanName\" } },\n { \"Column\": \"x_SkuRegion\", \"Properties\": { \"Field\": \"x_SkuRegion\" } },\n { \"Column\": \"x_SkuServiceFamily\", \"Properties\": { \"Field\": \"x_SkuServiceFamily\" } },\n { \"Column\": \"x_SkuTerm\", \"Properties\": { \"Field\": \"x_SkuTerm\" } },\n { \"Column\": \"x_SkuTier\", \"Properties\": { \"Field\": \"x_SkuTier\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } },\n { \"Column\": \"x_SubproductName\", \"Properties\": { \"Field\": \"x_SubproductName\" } },\n { \"Column\": \"x_UsageType\", \"Properties\": { \"Field\": \"x_UsageType\" } }\n]\n```\n\n// Costs_raw retention policy (clear historical data)\n.alter-merge table Costs_raw policy retention softdelete = 0d recoverability = disabled\n\n// Costs_raw retention policy (set the user-defined retention period)\n.alter-merge table Costs_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Costs_raw streaming ingestion (required for Fabric)\n.alter table Costs_raw policy streamingingestion disable\n\n\n//===| Prices |=========================================================================================================\n// NOTE: Must be before cost details.\n//\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_raw table -- Create the table if it doesn't exist\n.create-merge table Prices_raw ( ignore: string )\n\n// Prices_raw table -- Remove all columns to allow changing column types\n.alter table Prices_raw ( ignore: string )\n\n// Prices_raw table -- Redefine all columns\n.alter table Prices_raw (\n BasePrice: real, // Azure EA + MCA\n BillingAccountId: string, // Azure MCA\n BillingAccountName: string, // Azure MCA\n BillingCurrency: string, // Azure MCA\n BillingProfileId: string, // Azure MCA\n BillingProfileName: string, // Azure MCA\n Currency: string, // Azure MCA\n CurrencyCode: string, // Azure EA\n EffectiveEndDate: datetime, // Azure MCA\n EffectiveStartDate: datetime, // Azure EA + MCA\n EnrollmentNumber: string, // Azure EA\n IncludedQuantity: real, // Azure EA\n MarketPrice: real, // Azure EA + MCA\n MeterCategory: string, // Azure EA + MCA\n MeterId: string, // Azure MCA\n MeterID: string, // Azure EA\n MeterName: string, // Azure EA + MCA\n MeterRegion: string, // Azure EA + MCA\n MeterSubCategory: string, // Azure EA + MCA\n MeterType: string, // Azure EA + MCA\n OfferID: string, // Azure EA\n PartNumber: string, // Azure EA\n PriceType: string, // Azure EA + MCA\n Product: string, // Azure EA + MCA\n ProductId: string, // Azure MCA\n ProductID: string, // Azure EA\n ServiceFamily: string, // Azure EA + MCA\n SkuId: string, // Azure MCA\n SkuID: string, // Azure EA\n Term: string, // Azure EA + MCA\n TierMinimumUnits: real, // Azure MCA\n UnitOfMeasure: string, // Azure EA + MCA\n UnitPrice: real, // Azure EA + MCA\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Prices_raw ingestion mapping\n.create-or-alter table Prices_raw ingestion parquet mapping \"Prices_raw_mapping\"\n```\n[\n { \"Column\": \"BasePrice\", \"Properties\": { \"Field\": \"BasePrice\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\n { \"Column\": \"CurrencyCode\", \"Properties\": { \"Field\": \"CurrencyCode\" } },\n { \"Column\": \"EffectiveEndDate\", \"Properties\": { \"Field\": \"EffectiveEndDate\" } },\n { \"Column\": \"EffectiveStartDate\", \"Properties\": { \"Field\": \"EffectiveStartDate\" } },\n { \"Column\": \"EnrollmentNumber\", \"Properties\": { \"Field\": \"EnrollmentNumber\" } },\n { \"Column\": \"IncludedQuantity\", \"Properties\": { \"Field\": \"IncludedQuantity\" } },\n { \"Column\": \"MarketPrice\", \"Properties\": { \"Field\": \"MarketPrice\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterID\", \"Properties\": { \"Field\": \"MeterID\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"MeterType\", \"Properties\": { \"Field\": \"MeterType\" } },\n { \"Column\": \"OfferID\", \"Properties\": { \"Field\": \"OfferID\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PriceType\", \"Properties\": { \"Field\": \"PriceType\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductId\", \"Properties\": { \"Field\": \"ProductId\" } },\n { \"Column\": \"ProductID\", \"Properties\": { \"Field\": \"ProductID\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\n { \"Column\": \"SkuID\", \"Properties\": { \"Field\": \"SkuID\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"TierMinimumUnits\", \"Properties\": { \"Field\": \"TierMinimumUnits\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Prices_raw retention policy (clear historical data)\n.alter-merge table Prices_raw policy retention softdelete = 0d recoverability = disabled\n\n// Prices_raw retention policy (set the user-defined retention period)\n.alter-merge table Prices_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Prices_raw streaming ingestion (required for Fabric)\n.alter table Prices_raw policy streamingingestion disable\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_raw table -- Create the table if it doesn't exist\n.create-merge table Recommendations_raw ( ignore: string )\n\n// Recommendations_raw table -- Remove all columns to allow changing column types\n.alter table Recommendations_raw ( ignore: string )\n\n// Recommendations_raw table -- Redefine all columns\n.alter table Recommendations_raw (\n CostWithNoReservedInstances: real, // MS CM EA resv reco 2024-05-01\n CostWithNoReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n FirstUsageDate: datetime, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n InstanceFlexibilityGroup: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n InstanceFlexibilityRatio: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n Location: string, // MS CM EA+MCA resv reco 2024-05-01\n LookBackPeriod: string, // MS CM EA+MCA resv reco 2024-05-01\n MeterId: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n NetSavings: real, // MS CM EA resv reco 2024-05-01\n NetSavingsJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n NormalizedSize: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n ProviderName: string, // Hubs v1_2\n RecommendedQuantity: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n RecommendedQuantityNormalized: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n ResourceId: string, // Hubs v1_2\n ResourceName: string, // Hubs v1_2\n ResourceType: string, // Hubs v1_2, MS CM EA+MCA resv reco 2024-05-01\n Scope: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n SKU: string, // MS CM EA resv reco 2024-05-01\n SkuName: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces\n SkuProperties: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n SubAccountId: string, // Hubs v1_2\n SubAccountName: string, // Hubs v1_2\n SubscriptionId: string, // MS CM EA+MCA resv reco 2024-05-01\n Term: string, // MS CM EA+MCA resv reco 2024-05-01\n TotalCostWithReservedInstances: real, // MS CM EA resv reco 2024-05-01\n TotalCostWithReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n x_EffectiveCostAfter: real, // Hubs v1_2\n x_EffectiveCostBefore: real, // Hubs v1_2\n x_EffectiveCostSavings: real, // Hubs v1_2\n x_RecommendationCategory: string, // Hubs v1_2\n x_RecommendationDate: datetime, // Hubs v1_2\n x_RecommendationDescription: string, // Hubs v1_2\n x_RecommendationDetails: dynamic, // Hubs v1_2\n x_RecommendationId: string, // Hubs v1_2\n x_ResourceGroupName: string, // Hubs v1_2\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Recommendations_raw ingestion mapping\n.create-or-alter table Recommendations_raw ingestion parquet mapping \"Recommendations_raw_mapping\"\n```\n[\n { \"Column\": \"CostWithNoReservedInstances\", \"Properties\": { \"Field\": \"CostWithNoReservedInstances\" } },\n { \"Column\": \"CostWithNoReservedInstancesJson\", \"Properties\": { \"Field\": \"CostWithNoReservedInstancesJson\" } },\n { \"Column\": \"FirstUsageDate\", \"Properties\": { \"Field\": \"FirstUsageDate\" } },\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\n { \"Column\": \"Location\", \"Properties\": { \"Field\": \"Location\" } },\n { \"Column\": \"LookBackPeriod\", \"Properties\": { \"Field\": \"LookBackPeriod\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"NetSavings\", \"Properties\": { \"Field\": \"NetSavings\" } },\n { \"Column\": \"NetSavingsJson\", \"Properties\": { \"Field\": \"NetSavingsJson\" } },\n { \"Column\": \"NormalizedSize\", \"Properties\": { \"Field\": \"NormalizedSize\" } },\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\n { \"Column\": \"RecommendedQuantity\", \"Properties\": { \"Field\": \"RecommendedQuantity\" } },\n { \"Column\": \"RecommendedQuantityNormalized\", \"Properties\": { \"Field\": \"RecommendedQuantityNormalized\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\n { \"Column\": \"Scope\", \"Properties\": { \"Field\": \"Scope\" } },\n { \"Column\": \"SKU\", \"Properties\": { \"Field\": \"SKU\" } },\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\n { \"Column\": \"SkuProperties\", \"Properties\": { \"Field\": \"SkuProperties\" } },\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"TotalCostWithReservedInstances\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstances\" } },\n { \"Column\": \"TotalCostWithReservedInstancesJson\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstancesJson\" } },\n { \"Column\": \"x_EffectiveCostAfter\", \"Properties\": { \"Field\": \"x_EffectiveCostAfter\" } },\n { \"Column\": \"x_EffectiveCostBefore\", \"Properties\": { \"Field\": \"x_EffectiveCostBefore\" } },\n { \"Column\": \"x_EffectiveCostSavings\", \"Properties\": { \"Field\": \"x_EffectiveCostSavings\" } },\n { \"Column\": \"x_RecommendationCategory\", \"Properties\": { \"Field\": \"x_RecommendationCategory\" } },\n { \"Column\": \"x_RecommendationDate\", \"Properties\": { \"Field\": \"x_RecommendationDate\" } },\n { \"Column\": \"x_RecommendationDescription\", \"Properties\": { \"Field\": \"x_RecommendationDescription\" } },\n { \"Column\": \"x_RecommendationDetails\", \"Properties\": { \"Field\": \"x_RecommendationDetails\" } },\n { \"Column\": \"x_RecommendationId\", \"Properties\": { \"Field\": \"x_RecommendationId\" } },\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Recommendations_raw retention policy (clear historical data)\n.alter-merge table Recommendations_raw policy retention softdelete = 0d recoverability = disabled\n\n// Recommendations_raw retention policy (set the user-defined retention period)\n.alter-merge table Recommendations_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Recommendations_raw streaming ingestion (required for Fabric)\n.alter table Recommendations_raw policy streamingingestion disable\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_raw table -- Create the table if it doesn't exist\n.create-merge table Transactions_raw ( ignore: string )\n\n// Transactions_raw table -- Remove all columns to allow changing column types\n.alter table Transactions_raw ( ignore: string )\n\n// Transactions_raw table -- Redefine all columns\n.alter table Transactions_raw (\n AccountName: string, // MS CM EA resv trans 2023-05-01\n AccountOwnerEmail: string, // MS CM EA resv trans 2023-05-01\n Amount: real, // MS CM EA+MCA resv trans 2023-05-01\n ArmSkuName: string, // MS CM EA+MCA resv trans 2023-05-01\n BillingFrequency: string, // MS CM EA+MCA resv trans 2023-05-01\n BillingMonth: string, // MS CM EA resv trans 2023-05-01\n BillingProfileId: string, // MS CM MCA resv trans 2023-05-01\n BillingProfileName: string, // MS CM MCA resv trans 2023-05-01\n CostCenter: string, // MS CM EA resv trans 2023-05-01\n Currency: string, // MS CM EA+MCA resv trans 2023-05-01\n CurrentEnrollmentId: string, // MS CM EA resv trans 2023-05-01\n DepartmentName: string, // MS CM EA resv trans 2023-05-01\n Description: string, // MS CM EA+MCA resv trans 2023-05-01\n EventDate: datetime, // MS CM EA+MCA resv trans 2023-05-01\n EventType: string, // MS CM EA+MCA resv trans 2023-05-01\n Invoice: string, // MS CM EA+MCA resv trans 2023-05-01\n InvoiceId: string, // MS CM EA+MCA resv trans 2023-05-01\n InvoiceSectionId: string, // MS CM MCA resv trans 2023-05-01\n InvoiceSectionName: string, // MS CM MCA resv trans 2023-05-01\n MonetaryCommitment: real, // MS CM EA resv trans 2023-05-01\n Overage: real, // MS CM EA resv trans 2023-05-01\n PurchasingEnrollment: string, // MS CM EA resv trans 2023-05-01\n PurchasingSubscriptionGuid: string, // MS CM EA+MCA resv trans 2023-05-01\n PurchasingSubscriptionName: string, // MS CM EA+MCA resv trans 2023-05-01\n Quantity: real, // MS CM EA+MCA resv trans 2023-05-01\n Region: string, // MS CM EA+MCA resv trans 2023-05-01\n ReservationOrderId: string, // MS CM EA+MCA resv trans 2023-05-01\n ReservationOrderName: string, // MS CM EA+MCA resv trans 2023-05-01\n Term: string, // MS CM EA+MCA resv trans 2023-05-01\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Transactions_raw ingestion mapping\n.create-or-alter table Transactions_raw ingestion parquet mapping \"Transactions_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerEmail\", \"Properties\": { \"Field\": \"AccountOwnerEmail\" } },\n { \"Column\": \"Amount\", \"Properties\": { \"Field\": \"Amount\" } },\n { \"Column\": \"ArmSkuName\", \"Properties\": { \"Field\": \"ArmSkuName\" } },\n { \"Column\": \"BillingFrequency\", \"Properties\": { \"Field\": \"BillingFrequency\" } },\n { \"Column\": \"BillingMonth\", \"Properties\": { \"Field\": \"BillingMonth\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\n { \"Column\": \"CurrentEnrollmentId\", \"Properties\": { \"Field\": \"CurrentEnrollmentId\" } },\n { \"Column\": \"DepartmentName\", \"Properties\": { \"Field\": \"DepartmentName\" } },\n { \"Column\": \"Description\", \"Properties\": { \"Field\": \"Description\" } },\n { \"Column\": \"EventDate\", \"Properties\": { \"Field\": \"EventDate\" } },\n { \"Column\": \"EventType\", \"Properties\": { \"Field\": \"EventType\" } },\n { \"Column\": \"Invoice\", \"Properties\": { \"Field\": \"Invoice\" } },\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"InvoiceSectionName\", \"Properties\": { \"Field\": \"InvoiceSectionName\" } },\n { \"Column\": \"MonetaryCommitment\", \"Properties\": { \"Field\": \"MonetaryCommitment\" } },\n { \"Column\": \"Overage\", \"Properties\": { \"Field\": \"Overage\" } },\n { \"Column\": \"PurchasingEnrollment\", \"Properties\": { \"Field\": \"PurchasingEnrollment\" } },\n { \"Column\": \"PurchasingSubscriptionGuid\", \"Properties\": { \"Field\": \"PurchasingSubscriptionGuid\" } },\n { \"Column\": \"PurchasingSubscriptionName\", \"Properties\": { \"Field\": \"PurchasingSubscriptionName\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\n { \"Column\": \"ReservationOrderName\", \"Properties\": { \"Field\": \"ReservationOrderName\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Transactions_raw retention policy (clear historical data)\n.alter-merge table Transactions_raw policy retention softdelete = 0d recoverability = disabled\n\n// Transactions_raw retention policy (set the user-defined retention period)\n.alter-merge table Transactions_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Transactions_raw streaming ingestion (required for Fabric)\n.alter table Transactions_raw policy streamingingestion disable\n\n", + "$fxv#9": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Prices |=========================================================================================================\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All prices transformed to FOCUS 1.0. Use Prices_transform_v1_2() instead.', folder='Prices')\nPrices_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n let prices = materialize(\n Prices_raw\n //\n // Change real to decimal\n | extend\n BasePrice = todecimal(BasePrice),\n IncludedQuantity = todecimal(IncludedQuantity),\n MarketPrice = todecimal(MarketPrice),\n TierMinimumUnits = todecimal(TierMinimumUnits),\n UnitPrice = todecimal(UnitPrice)\n //\n | extend x_SkuId = coalesce(SkuId, SkuID)\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\n | extend x_SkuTerm = isoMonths(Term)\n | project-rename\n x_BaseUnitPrice = BasePrice,\n x_EffectivePeriodEnd = EffectiveEndDate,\n x_EffectivePeriodStart = EffectiveStartDate,\n x_PricingUnitDescription = UnitOfMeasure,\n x_SkuIncludedQuantity = IncludedQuantity,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuMeterType = MeterType,\n x_SkuOfferId = OfferID,\n x_SkuPartNumber = PartNumber,\n x_SkuPriceType = PriceType,\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTier = TierMinimumUnits\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, todecimal('')) // UnitPrice for savings plan is not the on-demand unit price\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, todecimal('')) // MarketPrice for savings plan is not the list price\n | extend ChargeCategory = case(\n x_SkuPriceType == 'Consumption', 'Usage',\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\n ''\n )\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\n //\n // Get latest ingested row based on the unique ID\n | extend x_IngestionTime = ingestion_time()\n );\n //\n // Meters for reservations and savings plans to identify commitment eligibility\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\n //\n // Copy list/base/contracted prices from on-demand SKUs\n prices\n | where x_SkuPriceType == 'SavingsPlan'\n // If we use join, specify the shuffle key\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\n //\n // Calculate commitment discount elgibility\n // TODO: Would a join be faster?\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\n //\n // Add PricingUnit and x_PricingBlockSize\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\n | lookup kind=leftouter (PricingUnits | extend x_PricingBlockSize = todecimal(x_PricingBlockSize)) on x_PricingUnitDescription\n //\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, todecimal('')) // Savings plan prices are for the effective price, not the contracted price\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\n | project\n BillingAccountId = tolower(case(\n BillingProfileId startswith '/', BillingProfileId,\n BillingAccountId startswith '/', BillingAccountId,\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\n )),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\n ChargeCategory,\n CommitmentDiscountCategory = case(\n x_SkuPriceType == 'ReservedInstance', 'Usage',\n x_SkuPriceType == 'SavingsPlan', 'Spend',\n ''\n ),\n CommitmentDiscountType = case(\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\n ''\n ),\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory = case(\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed',\n ''\n ),\n PricingUnit,\n SkuId = coalesce(ProductId, ProductID),\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement = case(\n strlen(x_BillingAccountId) > 32, 'MCA',\n strlen(x_BillingAccountId) < 32, 'EA',\n 'Unknown'\n ),\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingCurrency = coalesce(Currency, CurrencyCode), // CurrencyCode last as a fallback only\n x_PricingSubcategory = case(\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\n ''\n ),\n x_PricingUnitDescription,\n x_SkuDescription = Product,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\n}\n\n// Prices_final_v1_0 table\n.create-merge table Prices_final_v1_0 (\n BillingAccountId: string,\n BillingAccountName: string,\n BillingCurrency: string,\n ChargeCategory: string,\n CommitmentDiscountCategory: string,\n CommitmentDiscountType: string,\n ContractedUnitPrice: decimal,\n ListUnitPrice: decimal,\n PricingCategory: string,\n PricingUnit: string,\n SkuId: string,\n SkuPriceId: string,\n SkuPriceIdv2: string, // Hubs add-on\n x_BaseUnitPrice: decimal, // Azure\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure MCA\n x_BillingProfileId: string, // Azure MCA\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_ContractedUnitPriceDiscount: decimal, // Hubs add-on\n x_ContractedUnitPriceDiscountPercent: decimal, // Hubs add-on\n x_EffectivePeriodEnd: datetime, // Azure\n x_EffectivePeriodStart: datetime, // Azure\n x_EffectiveUnitPrice: decimal, // Azure\n x_EffectiveUnitPriceDiscount: decimal, // Hubs add-on\n x_EffectiveUnitPriceDiscountPercent: decimal, // Hubs add-on\n x_IngestionTime: datetime, // Hubs add-on\n x_PricingBlockSize: decimal, // Hubs add-on\n x_PricingCurrency: string, // Azure\n x_PricingSubcategory: string, // Hubs add-on\n x_PricingUnitDescription: string, // Azure\n x_SkuDescription: string, // Azure\n x_SkuId: string, // Azure\n x_SkuIncludedQuantity: decimal, // Azure EA\n x_SkuMeterCategory: string, // Azure\n x_SkuMeterId: string, // Azure\n x_SkuMeterName: string, // Azure\n x_SkuMeterSubcategory: string, // Azure\n x_SkuMeterType: string, // Azure\n x_SkuPriceType: string, // Azure\n x_SkuProductId: string, // Azure\n x_SkuRegion: string, // Azure\n x_SkuServiceFamily: string, // Azure\n x_SkuOfferId: string, // Azure EA\n x_SkuPartNumber: string, // Azure EA\n x_SkuTerm: int, // Azure\n x_SkuTier: decimal, // Azure MCA\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_TotalUnitPriceDiscount: decimal, // Hubs add-on\n x_TotalUnitPriceDiscountPercent: decimal // Hubs add-on\n)\n\n// Update policy for Prices_raw -> Prices_final_v1_0\n.alter table Prices_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Prices_raw\",\n \"Query\": \"Prices_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Cost and usage |=================================================================================================\n// Supported versions:\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All costs transformed to FOCUS 1.0. Use Costs_transform_v1_2() instead.', folder='Costs')\nCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n Costs_raw\n //\n // Change real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n ContractedCost = todecimal(ContractedCost),\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n EffectiveCost = todecimal(EffectiveCost),\n ListCost = todecimal(ListCost),\n ListUnitPrice = todecimal(ListUnitPrice),\n PricingQuantity = todecimal(PricingQuantity),\n UsageAmount = todecimal(UsageAmount),\n UsageQuantity = todecimal(UsageQuantity),\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\n x_Cost = todecimal(x_Cost),\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\n x_OnDemandCost = todecimal(x_OnDemandCost),\n x_OnDemandCostInUsd = todecimal(x_OnDemandCostInUsd),\n x_OnDemandUnitPrice = todecimal(x_OnDemandUnitPrice),\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\n //\n // Dedupe rows\n | extend x_IngestionTime = ingestion_time()\n | extend x_ChargeId = ''\n // TODO: Consider adding a unique charge ID per row\n // hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // // 1. Resource hierarchy (including resource name), highest to lowest\n // BillingAccountId,\n // x_InvoiceSectionId,\n // x_AccountOwnerId,\n // SubAccountId,\n // x_ResourceGroupName,\n // ResourceName,\n // // 2. Resource details\n // ResourceId,\n // RegionId,\n // Tags,\n // CommitmentDiscountId,\n // x_CostCenter,\n // // 4. Meter details\n // SkuPriceId,\n // x_SkuMeterId,\n // x_SkuPartNumber,\n // x_SkuOfferId,\n // x_SkuDetails,\n // // 5. Date\n // ChargePeriodStart\n // ))\n //\n // Identify data quality issues\n | extend x_SourceChanges = trim_end(',', strcat(\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\n 'XEffectiveUnitPriceRoundingError,', ''),\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\n ))\n //\n // Fix columns needed in other changes\n | extend ProviderName = case(\n isnotempty(ProviderName), ProviderName,\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\n ''\n )\n //\n // Identify source\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\n ''\n ))\n // Append version check error code\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\n )\n //\n // Fix quantities\n | extend PricingQuantity = case(\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\n PricingQuantity\n )\n | extend ConsumedQuantity = case(\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, decimal(1)),\n ConsumedQuantity\n )\n //\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\n and (ListUnitPrice == 0 or ContractedUnitPrice == 0)\n and x_EffectiveUnitPrice != 0\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\n | as allCosts\n | where tmp_MissingPrices\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | as costsWithMissingPrices\n | join kind=leftouter (\n Prices_final_v1_0\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\n ) on tmp_ReservationPriceLookupKey\n //\n // Select the best price to use for each row\n // TODO: Save values before changing -- | extend x_old_ContractedUnitPrice = ContractedUnitPrice, x_old_EffectiveUnitPrice = x_EffectiveUnitPrice, x_old_ListUnitPrice = ListUnitPrice, x_old_ListCost = ListCost, x_old_ContractedCost = ContractedCost\n | extend x_EffectiveUnitPrice = case(\n // If price is a rounding error away from the billed price, use the billed price\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\n // If price is a rounding error away from the contracted price, use the contracted price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\n x_EffectiveUnitPrice\n )\n | extend ContractedUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\n x_EffectiveUnitPrice\n )\n | extend ListUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // Otherwise, assume the contracted price is the same as list price to support aggregations\n ContractedUnitPrice\n )\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\n | extend ContractedCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\n // ContractedCost is 0 in all other scenarios...\n // If 0 and there's a billed cost and prices are the same, use BilledCost\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume EffectiveCost\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\n // Fall back to the original value for any unhandled scenarios\n ContractedCost\n )\n | extend ListCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\n // ListCost is 0 in all other scenarios...\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume ContractedCost\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\n // Fall back to the original value for any unhandled scenarios\n ListCost\n )\n // Merge the rest of the unmodified cost records and remove excess columns\n | union (allCosts | where not(tmp_MissingPrices))\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\n //\n // BUG: Fix ContractedCost that has bad values\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\n //\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), todecimal(''))\n | extend ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\n //\n // Convert IDs to lowercase for consistency\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\n //\n // BUG: Remove EffectiveCost for commitment discount purchases\n | extend EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), EffectiveCost)\n | extend x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), x_EffectiveCostInUsd)\n //\n // Clean up resource columns\n | extend ResourceId = case(\n isnotempty(ResourceId), ResourceId,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\n ResourceId)\n | extend ResourceName = tolower(case(\n isnotempty(ResourceName), ResourceName,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\n ResourceName))\n | extend x_ResourceType = case(\n isnotempty(x_ResourceType), x_ResourceType,\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\n x_ResourceType)\n | extend ResourceType = case(\n // Use existing resource type display name unless it's an internal resource type ID\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\n // Use CommitmentDiscountType for commitment discount purchases\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\n // Look up display name from internal type\n isnotempty(x_ResourceType), coalesce(resource_type(x_ResourceType).SingularDisplayName, ResourceType, x_ResourceType),\n ResourceType)\n //\n // Sort columns and apply final transforms\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId = tolower(BillingAccountId),\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEnd),\n BillingPeriodStart = startofmonth(BillingPeriodStart),\n ChargeCategory = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Credit', 'Credit',\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\n ChargeCategory\n ),\n ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass),\n ChargeDescription,\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\n ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency),\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId = tolower(CommitmentDiscountId),\n CommitmentDiscountName,\n CommitmentDiscountStatus = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Used Commitment', 'Used',\n ChargeSubcategory == 'Unused Commitment', 'Unused',\n CommitmentDiscountStatus\n ),\n CommitmentDiscountType,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\n EffectiveCost,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory = case(\n // Handle FOCUS 1.0-preview PricingCategory values\n PricingCategory == 'On-Demand', 'Standard',\n PricingCategory == 'Commitment-Based', 'Committed',\n PricingCategory\n ),\n PricingQuantity,\n PricingUnit,\n ProviderName,\n // Handle missing PublisherName values\n PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, ''),\n // Handle FOCUS 1.0-preview Region column\n RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region)),\n RegionName = coalesce(RegionName, Region),\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SkuId,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType, // Azure 1.0-preview(v1)+\n Tags = parse_json(Tags),\n x_AccountId, // Azure 1.0-preview(v1)+\n x_AccountName, // Azure 1.0-preview(v1)+\n x_AccountOwnerId, // Azure 1.0-preview(v1)+\n x_BilledCostInUsd, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement = case(\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\n ProviderName\n ), // Hubs add-on\n x_BillingAccountId, // Azure 1.0-preview(v1)+\n x_BillingAccountName, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate, // Azure 1.0-preview(v1)+\n x_BillingProfileId, // Azure 1.0-preview(v1)+\n x_BillingProfileName, // Azure 1.0-preview(v1)+\n x_ChargeId, // Azure 1.0-preview(v1) only\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd), // Azure 1.0+\n x_CostAllocationRuleName, // Azure 1.0-preview(v1)+\n x_CostCategories = parse_json(x_CostCategories), // AWS 1.0 (JSON)\n x_CostCenter, // Azure 1.0-preview(v1)+\n x_Credits = parse_json(x_Credits), // GCP Jan 2024\n x_CostType, // GCP Jan 2024\n x_CurrencyConversionRate, // GCP Jun 2024\n x_CustomerId, // Azure 1.0-preview(v1)+\n x_CustomerName, // Azure 1.0-preview(v1)+\n x_Discount = parse_json(x_Discount), // AWS 1.0 (JSON)\n x_EffectiveCostInUsd, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice, // Azure 1.0-preview(v1)+\n x_ExportTime, // GCP Jan 2024\n x_IngestionTime, // Hubs add-on\n x_InvoiceId = coalesce(InvoiceId, x_InvoiceId), // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId = case( // Azure 1.0-preview(v1)+\n x_InvoiceSectionId == '-2', '',\n x_InvoiceSectionId\n ),\n x_InvoiceSectionName = case( // Azure 1.0-preview(v1)+\n x_InvoiceSectionName == 'Unassigned', '',\n x_InvoiceSectionName\n ),\n x_ListCostInUsd, // Azure 1.0-preview(v1)+\n x_Location, // GCP Jan 2024\n x_Operation, // AWS 1.0\n x_PartnerCreditApplied, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate, // Azure 1.0-preview(v1)+\n x_PricingBlockSize, // Azure 1.0-preview(v1)+\n x_PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency), // Azure 1.0-preview(v1)+\n x_PricingSubcategory, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription, // Azure 1.0-preview(v1)+\n x_Project, // GCP Jan 2024\n x_PublisherCategory, // Azure 1.0-preview(v1)+\n x_PublisherId, // Azure 1.0-preview(v1)+\n x_ResellerId, // Azure 1.0-preview(v1)+\n x_ResellerName, // Azure 1.0-preview(v1)+\n x_ResourceGroupName = tolower(x_ResourceGroupName), // Azure 1.0-preview(v1)+\n x_ResourceType, // Azure 1.0-preview(v1)+\n x_ServiceCode, // AWS 1.0\n x_ServiceId, // GCP Jan 2024\n x_ServicePeriodEnd, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart, // Azure 1.0-preview(v1)+\n x_SkuDescription, // Azure 1.0-preview(v1)+\n x_SkuDetails = parse_json(x_SkuDetails), // Azure 1.0-preview(v1)+\n x_SkuIsCreditEligible, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory, // Azure 1.0-preview(v1)+\n x_SkuMeterId, // Azure 1.0-preview(v1)+\n x_SkuMeterName = coalesce(SkuMeter, x_SkuMeterName), // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory, // Azure 1.0-preview(v1)+\n x_SkuOfferId, // Azure 1.0-preview(v1)+\n x_SkuOrderId, // Azure 1.0-preview(v1)+\n x_SkuOrderName, // Azure 1.0-preview(v1)+\n x_SkuPartNumber, // Azure 1.0-preview(v1)+\n x_SkuRegion, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily, // Azure 1.0-preview(v1)+\n x_SkuTerm, // Azure 1.0-preview(v1)+\n x_SkuTier, // Azure 1.0-preview(v1)+\n x_SourceChanges, // Hubs add-on\n x_SourceName, // Hubs add-on\n x_SourceProvider, // Hubs add-on\n x_SourceType, // Hubs add-on\n x_SourceVersion, // Hubs add-on\n x_UsageType // AWS 1.0\n}\n\n// Costs_final_v1_0 table\n.create-merge table Costs_final_v1_0 (\n AvailabilityZone: string,\n BilledCost: decimal,\n BillingAccountId: string,\n BillingAccountName: string,\n BillingAccountType: string, // Azure 1.0-preview(v1)+\n BillingCurrency: string,\n BillingPeriodEnd: datetime,\n BillingPeriodStart: datetime,\n ChargeCategory: string,\n ChargeClass: string,\n ChargeDescription: string,\n ChargeFrequency: string,\n ChargePeriodEnd: datetime,\n ChargePeriodStart: datetime,\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview only\n CommitmentDiscountId: string,\n CommitmentDiscountName: string,\n CommitmentDiscountStatus: string,\n CommitmentDiscountType: string,\n ConsumedQuantity: decimal,\n ConsumedUnit: string,\n ContractedCost: decimal,\n ContractedUnitPrice: decimal,\n EffectiveCost: decimal,\n InvoiceIssuerName: string,\n ListCost: decimal,\n ListUnitPrice: decimal,\n PricingCategory: string,\n PricingQuantity: decimal,\n PricingUnit: string,\n ProviderName: string,\n PublisherName: string,\n RegionId: string,\n RegionName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n ServiceCategory: string,\n ServiceName: string,\n SkuId: string,\n SkuPriceId: string,\n SubAccountId: string,\n SubAccountName: string,\n SubAccountType: string,\n Tags: dynamic,\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_BilledCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: decimal, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: decimal, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_ContractedCostInUsd: decimal, // Azure 1.0+\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_Credits: dynamic, // GCP Jan 2024\n x_CostType: string, // GCP Jan 2024\n x_CurrencyConversionRate: decimal, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: dynamic, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: decimal, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024\n x_IngestionTime: datetime, // Hubs add-on\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_Operation: string, // AWS 1.0\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: decimal, // Azure 1.0-preview(v1)+\n x_PricingCurrency: string, // Azure 1.0-preview(v1)+\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterName: string, // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceChanges: string, // Hubs add-on\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_UsageType: string // AWS 1.0\n)\n\n// Update policy for Costs_raw -> Costs_final_v1_0 table\n.alter table Costs_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Costs_raw\",\n \"Query\": \"Costs_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Actual costs |===================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use ActualCosts_transform_v1_2() instead.', folder='Costs')\nActualCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n // TODO: Transform actual costs to FOCUS 1.0 format\n ActualCosts_raw\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodStart = Date,\n ChargePeriodEnd = Date + 1d,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = '',\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory = '',\n SkuId = '',\n SkuMeter = '',\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = SubscriptionName,\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentName = '',\n x_ComponentType = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel = '',\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for ActualCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": false,\n \"Source\": \"ActualCosts_raw\",\n \"Query\": \"ActualCosts_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Amortized costs |================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use AmortizedCosts_transform_v1_2() instead.', folder='Costs')\nAmortizedCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n // TODO: Transform actual costs to FOCUS 1.0 format\n AmortizedCosts_raw\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodStart = Date,\n ChargePeriodEnd = Date + 1d,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = '',\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory = '',\n SkuId = '',\n SkuMeter = '',\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = SubscriptionName,\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentName = '',\n x_ComponentType = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel = '',\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for AmortizedCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": false,\n \"Source\": \"AmortizedCosts_raw\",\n \"Query\": \"AmortizedCosts_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All commitment discount usage transformed to FOCUS 1.0. This includes reservationdeatils_raw. Use CommitmentDiscountUsage_transform_v1_2() instead.', folder='Commitment discounts')\nCommitmentDiscountUsage_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n CommitmentDiscountUsage_raw\n //\n // Change real to decimal\n | extend\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\n ReservedHours = todecimal(ReservedHours),\n TotalReservedQuantity = todecimal(TotalReservedQuantity),\n UsedHours = todecimal(UsedHours)\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Handle resource columns\n | extend ResourceId = tolower(InstanceId)\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, x_ServiceModel) on x_ResourceType\n //\n // Sort columns and apply final transforms\n | project\n ChargePeriodEnd = UsageDate + 1d,\n ChargePeriodStart = UsageDate,\n CommitmentDiscountCategory = 'Usage',\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\n CommitmentDiscountType = 'Reservation',\n ConsumedQuantity = UsedHours,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SubAccountId,\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\n x_CommitmentDiscountCommittedAmount = ReservedHours,\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\n x_CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\n x_IngestionTime = ingestion_time(),\n x_ResourceGroupName,\n x_ResourceType,\n // x_RowId = hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // CommitmentDiscountId,\n // ResourceId,\n // ChargePeriodStart\n // )),\n x_ServiceModel,\n x_SkuOrderId = ReservationOrderId,\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\n}\n\n// CommitmentDiscountUsage_final_v1_0 table\n.create-merge table CommitmentDiscountUsage_final_v1_0 (\n ChargePeriodEnd: datetime, // Hubs add-on\n ChargePeriodStart: datetime, // MS 2023-03-01\n CommitmentDiscountCategory: string, // Hubs add-on\n CommitmentDiscountId: string, // MS 2023-03-01\n CommitmentDiscountType: string, // Hubs add-on\n ConsumedQuantity: decimal, // MS 2023-03-01\n ProviderName: string, // Hubs add-on\n ResourceId: string, // MS 2023-03-01\n ResourceName: string, // Hubs add-on\n ResourceType: string, // Hubs add-on\n ServiceCategory: string, // Hubs add-on\n ServiceName: string, // Hubs add-on\n SubAccountId: string, // Hubs add-on\n x_CommitmentDiscountCommittedCount: decimal, // MS 2023-03-01\n x_CommitmentDiscountCommittedAmount: decimal, // MS 2023-03-01\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\n x_CommitmentDiscountNormalizedRatio: decimal, // MS 2023-03-01\n x_CommitmentDiscountQuantity: decimal, // MS 2023-03-01\n x_IngestionTime: datetime, // Hubs add-on\n x_ResourceGroupName: string, // Hubs add-on\n x_ResourceType: string, // Hubs add-on\n x_ServiceModel: string, // Hubs add-on\n x_SkuOrderId: string, // MS 2023-03-01\n x_SkuSize: string, // MS 2023-03-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string // Hubs add-on\n)\n\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_0 table\n.alter table CommitmentDiscountUsage_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"CommitmentDiscountUsage_raw\",\n \"Query\": \"CommitmentDiscountUsage_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All recommendations transformed to FOCUS 1.0. Use Recommendations_transform_v1_2() instead.', folder='Recommendations')\nRecommendations_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Recommendations_raw\n //\n // Change real to decimal\n | extend\n CostWithNoReservedInstances = todecimal(CostWithNoReservedInstances),\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\n NetSavings = todecimal(NetSavings),\n RecommendedQuantity = todecimal(RecommendedQuantity),\n RecommendedQuantityNormalized = todecimal(RecommendedQuantityNormalized),\n TotalCostWithReservedInstances = todecimal(TotalCostWithReservedInstances)\n //\n | extend x_IngestionTime = ingestion_time()\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Convert JSON cost columns to decimal\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\n //\n // Build recommendation details\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\n | extend x_RecommendationDetails = case(\n x_SourceType == 'ReservationRecommendations', bag_pack(\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\n 'CommitmentDiscountResourceType', ResourceType,\n 'CommitmentDiscountScope', Scope,\n 'LookbackPeriodDuration', case(\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\n ''\n ),\n 'LookbackPeriodStart', FirstUsageDate,\n 'RecommendedQuantity', RecommendedQuantity,\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\n 'RegionId', Location,\n 'RegionName', RegionName,\n 'SkuMeterId', MeterId,\n 'SkuPriceDetails', SkuProperties,\n 'SkuSize', coalesce(SKU, SkuName),\n 'SkuTerm', isoMonths(Term)\n ),\n dynamic({})\n )\n //\n // Sort columns and apply final transforms\n | extend x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d)\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\n | project\n ProviderName,\n SubAccountId = iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), ''),\n x_IngestionTime,\n x_EffectiveCostAfter = TotalCostWithReservedInstances,\n x_EffectiveCostBefore = CostWithNoReservedInstances,\n x_EffectiveCostSavings = NetSavings,\n x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d),\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n// Recommendations_final_v1_0 table\n.create-merge table Recommendations_final_v1_0 (\n ProviderName: string,\n SubAccountId: string,\n x_IngestionTime: datetime,\n x_EffectiveCostAfter: decimal,\n x_EffectiveCostBefore: decimal,\n x_EffectiveCostSavings: decimal,\n x_RecommendationDate: datetime,\n x_RecommendationDetails: dynamic,\n x_SourceName: string,\n x_SourceProvider: string,\n x_SourceType: string,\n x_SourceVersion: string\n)\n\n// Update policy for Recommendations_raw -> Recommendations_final_v1_0 table\n.alter table Recommendations_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Recommendations_raw\",\n \"Query\": \"Recommendations_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All transactions transformed to FOCUS 1.0. Use Transactions_transform_v1_2() instead.', folder='Transactions')\nTransactions_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Transactions_raw\n //\n // Change real to decimal\n | extend\n Amount = todecimal(Amount),\n MonetaryCommitment = todecimal(MonetaryCommitment),\n Overage = todecimal(Overage),\n Quantity = todecimal(Quantity)\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Handle BillingPeriodStart/End\n | extend BillingMonth = tostring(BillingMonth)\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\n //\n // Sort columns and apply final transforms\n | project\n BilledCost = Amount,\n BillingAccountId = case(\n BillingProfileId startswith '/', BillingProfileId,\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\n ''\n ),\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\n BillingCurrency = Currency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory = case(\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = case(\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\n EventType == 'Refund', 'Correction',\n ''\n ),\n ChargeDescription = Description,\n ChargeFrequency = case(\n BillingFrequency == 'OneTime', 'One-Time',\n BillingFrequency == 'Recurring', 'Recurring',\n BillingFrequency\n ),\n ChargePeriodStart = EventDate,\n PricingQuantity = Quantity,\n PricingUnit = 'Reservations',\n ProviderName,\n RegionId = Region,\n RegionName = Region,\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerEmail,\n x_CostCenter = CostCenter,\n x_InvoiceId = InvoiceId,\n x_InvoiceNumber = Invoice,\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\n x_IngestionTime = ingestion_time(),\n x_MonetaryCommitment = MonetaryCommitment,\n x_Overage = Overage,\n x_PurchasingBillingAccountId = PurchasingEnrollment,\n x_SkuOrderId = ReservationOrderId,\n x_SkuOrderName = ReservationOrderName,\n x_SkuSize = ArmSkuName,\n x_SkuTerm = isoMonths(Term),\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId = PurchasingSubscriptionGuid,\n x_TransactionType = EventType\n}\n\n// Transactions_final_v1_0 table\n.create-merge table Transactions_final_v1_0 (\n BilledCost: decimal, // MS CM EA+MCA 2023-05-01\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n ChargeCategory: string, // Hubs add-on\n ChargeClass: string, // Hubs add-on\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n PricingQuantity: decimal, // MS CM EA+MCA 2023-05-01\n PricingUnit: string, // Hubs add-on\n ProviderName: string, // Hubs add-on\n RegionId: string, // MS CM EA+MCA 2023-05-01\n RegionName: string, // MS CM EA+MCA 2023-05-01\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\n x_AccountName: string, // MS CM EA 2023-05-01\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\n x_CostCenter: string, // MS CM EA 2023-05-01\n x_InvoiceId: string, // MS CM MCA 2023-05-01\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\n x_IngestionTime: datetime, // Hubs add-on\n x_MonetaryCommitment: decimal, // MS CM EA 2023-05-01\n x_Overage: decimal, // MS CM EA 2023-05-01\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\n)\n\n// Update policy for Transactions_raw -> Transactions_final_v1_0 table\n.alter table Transactions_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Transactions_raw\",\n \"Query\": \"Transactions_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n", + "CONFIG": "config", + "HUB_DATA_EXPLORER": "hubDataExplorer", + "HUB_DB": "Hub", + "INGESTION": "ingestion", + "INGESTION_DB": "Ingestion", + "INGESTION_ID_SEPARATOR": "__", + "ftkReleaseUri": "[if(endsWith(variables('finOpsToolkitVersion'), '-dev'), 'https://github.com/microsoft/finops-toolkit/releases/latest/download', format('https://github.com/microsoft/finops-toolkit/releases/download/v{0}', variables('finOpsToolkitVersion')))]", + "useFabric": "[not(empty(parameters('fabricQueryUri')))]", + "useAzure": "[and(not(variables('useFabric')), not(empty(parameters('clusterName'))))]", + "dataExplorerPrivateDnsZoneName": "[replace(format('privatelink.{0}.{1}', parameters('app').hub.location, replace(environment().suffixes.storage, 'core', 'kusto')), '..', '.')]", + "ingestionCapacity": { + "Dev(No SLA)_Standard_E2a_v4": 1, + "Dev(No SLA)_Standard_D11_v2": 1, + "Standard_D11_v2": 2, + "Standard_D12_v2": 4, + "Standard_D13_v2": 8, + "Standard_D14_v2": 16, + "Standard_D16d_v5": 16, + "Standard_D32d_v4": 32, + "Standard_D32d_v5": 32, + "Standard_DS13_v2+1TB_PS": 8, + "Standard_DS13_v2+2TB_PS": 8, + "Standard_DS14_v2+3TB_PS": 16, + "Standard_DS14_v2+4TB_PS": 16, + "Standard_E2a_v4": 2, + "Standard_E2ads_v5": 2, + "Standard_E2d_v4": 2, + "Standard_E2d_v5": 2, + "Standard_E4a_v4": 4, + "Standard_E4ads_v5": 4, + "Standard_E4d_v4": 4, + "Standard_E4d_v5": 4, + "Standard_E8a_v4": 8, + "Standard_E8ads_v5": 8, + "Standard_E8as_v4+1TB_PS": 8, + "Standard_E8as_v4+2TB_PS": 8, + "Standard_E8as_v5+1TB_PS": 8, + "Standard_E8as_v5+2TB_PS": 8, + "Standard_E8d_v4": 8, + "Standard_E8d_v5": 8, + "Standard_E8s_v4+1TB_PS": 8, + "Standard_E8s_v4+2TB_PS": 8, + "Standard_E8s_v5+1TB_PS": 8, + "Standard_E8s_v5+2TB_PS": 8, + "Standard_E16a_v4": 16, + "Standard_E16ads_v5": 16, + "Standard_E16as_v4+3TB_PS": 16, + "Standard_E16as_v4+4TB_PS": 16, + "Standard_E16as_v5+3TB_PS": 16, + "Standard_E16as_v5+4TB_PS": 16, + "Standard_E16d_v4": 16, + "Standard_E16d_v5": 16, + "Standard_E16s_v4+3TB_PS": 16, + "Standard_E16s_v4+4TB_PS": 16, + "Standard_E16s_v5+3TB_PS": 16, + "Standard_E16s_v5+4TB_PS": 16, + "Standard_E64i_v3": 64, + "Standard_E80ids_v4": 80, + "Standard_EC8ads_v5": 8, + "Standard_EC8as_v5+1TB_PS": 8, + "Standard_EC8as_v5+2TB_PS": 8, + "Standard_EC16ads_v5": 16, + "Standard_EC16as_v5+3TB_PS": 16, + "Standard_EC16as_v5+4TB_PS": 16, + "Standard_L4s": 4, + "Standard_L8as_v3": 8, + "Standard_L8s": 8, + "Standard_L8s_v2": 8, + "Standard_L8s_v3": 8, + "Standard_L16as_v3": 16, + "Standard_L16s": 16, + "Standard_L16s_v2": 16, + "Standard_L16s_v3": 16, + "Standard_L32as_v3": 32, + "Standard_L32s_v3": 32 + }, + "dataExplorerIngestionCapacity": "[if(variables('useFabric'), parameters('fabricCapacityUnits'), if(not(variables('useAzure')), 1, coalesce(tryGet(variables('ingestionCapacity'), parameters('clusterSku')), 1)))]", + "dataExplorerUri": "[if(variables('useFabric'), parameters('fabricQueryUri'), format('https://{0}.{1}.kusto.windows.net', replace(parameters('clusterName'), '_', '-'), parameters('app').hub.location))]", + "finOpsToolkitVersion": "12.0" + }, + "resources": { + "cluster::adfClusterAdmin": { + "condition": "[variables('useAzure')]", + "type": "Microsoft.Kusto/clusters/principalAssignments", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), 'adf-mi-cluster-admin')]", + "properties": { + "principalType": "App", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[reference('dataFactory', '2018-06-01', 'full').identity.tenantId]", + "role": "AllDatabasesAdmin" + }, + "dependsOn": [ + "cluster", + "dataFactory" + ] + }, + "cluster::ingestionDb": { + "condition": "[variables('useAzure')]", + "type": "Microsoft.Kusto/clusters/databases", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), variables('INGESTION_DB'))]", + "location": "[parameters('app').hub.location]", + "kind": "ReadWrite", + "dependsOn": [ + "cluster" + ] + }, + "cluster::hubDb": { + "condition": "[variables('useAzure')]", + "type": "Microsoft.Kusto/clusters/databases", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), variables('HUB_DB'))]", + "location": "[parameters('app').hub.location]", + "kind": "ReadWrite", + "dependsOn": [ + "cluster" + ] + }, + "dataFactoryVNet::dataExplorerManagedPrivateEndpoint": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', variables('HUB_DATA_EXPLORER'))]", + "properties": { + "name": "[variables('HUB_DATA_EXPLORER')]", + "groupId": "cluster", + "privateLinkResourceId": "[resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-'))]", + "fqdns": [ + "[format('https://{0}.{1}.kusto.windows.net', replace(parameters('clusterName'), '_', '-'), parameters('app').hub.location)]" + ] + }, + "dependsOn": [ + "appRegistration", + "cluster" + ] + }, + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "dependsOn": [ + "appRegistration" + ] + }, + "blobPrivateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "dependsOn": [ + "appRegistration" + ] + }, + "queuePrivateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.queue.{0}', environment().suffixes.storage)]", + "dependsOn": [ + "appRegistration" + ] + }, + "tablePrivateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.table.{0}', environment().suffixes.storage)]", + "dependsOn": [ + "appRegistration" + ] + }, + "storage": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]", + "dependsOn": [ + "appRegistration" + ] + }, + "cluster": { + "condition": "[variables('useAzure')]", + "type": "Microsoft.Kusto/clusters", + "apiVersion": "2023-08-15", + "name": "[replace(parameters('clusterName'), '_', '-')]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Kusto/clusters'), createObject()))]", + "sku": { + "name": "[parameters('clusterSku')]", + "tier": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 'Basic', 'Standard')]", + "capacity": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 1, if(equals(parameters('clusterCapacity'), 1), 2, parameters('clusterCapacity')))]" + }, + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "enableStreamingIngest": true, + "enableAutoStop": false, + "publicNetworkAccess": "[if(parameters('app').hub.options.privateRouting, 'Disabled', 'Enabled')]" + }, + "dependsOn": [ + "appRegistration" + ] + }, + "clusterStorageAccess": { + "condition": "[variables('useAzure')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(replace(parameters('clusterName'), '_', '-'), subscription().id, 'Storage Blob Data Contributor')]", + "properties": { + "description": "Give \"Storage Blob Data Contributor\" to the cluster", + "principalId": "[reference('cluster', '2023-08-15', 'full').identity.principalId]", + "principalType": "ServicePrincipal", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" + }, + "dependsOn": [ + "appRegistration", + "cluster" + ] + }, + "dataExplorerPrivateDnsZone": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[variables('dataExplorerPrivateDnsZoneName')]", + "location": "global", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateDnsZones'), createObject()))]", + "properties": {} + }, + "dataExplorerPrivateDnsZoneLink": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', variables('dataExplorerPrivateDnsZoneName'), format('{0}-link', replace(variables('dataExplorerPrivateDnsZoneName'), '.', '-')))]", + "location": "global", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateDnsZones/virtualNetworkLinks'), createObject()))]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "dataExplorerPrivateDnsZone" + ] + }, + "dataExplorerEndpoint": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', replace(parameters('clusterName'), '_', '-'))]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateEndpoints'), createObject()))]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.dataExplorer]" + }, + "privateLinkServiceConnections": [ + { + "name": "dataExplorerLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-'))]", + "groupIds": [ + "cluster" + ] } - }, + } + ] + }, + "dependsOn": [ + "cluster" + ] + }, + "dataExplorerPrivateDnsZoneGroup": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', replace(parameters('clusterName'), '_', '-')), 'dataExplorer-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ { - "name": "Set Hub Dataset", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "hubDataset", - "value": { - "value": "@if(equals(toLower(variables('exportDatasetType')), 'focuscost'), 'Costs', if(equals(toLower(variables('exportDatasetType')), 'pricesheet'), 'Prices', if(equals(toLower(variables('exportDatasetType')), 'reservationdetails'), 'CommitmentDiscountUsage', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Recommendations', if(equals(toLower(variables('exportDatasetType')), 'reservationtransactions'), 'Transactions', if(equals(toLower(variables('exportDatasetType')), 'actualcost'), 'ActualCosts', if(equals(toLower(variables('exportDatasetType')), 'amortizedcost'), 'AmortizedCosts', toLower(variables('exportDatasetType')))))))))", - "type": "Expression" - } + "name": "privatelink-westus-kusto-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" } }, { - "name": "Set Destination Folder", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check Schema", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Hub Dataset", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "destinationFolder", - "value": { - "value": "@replace(concat(variables('hubDataset'),'/',substring(variables('date'), 0, 4),'/',substring(variables('date'), 4, 2),'/',toLower(variables('scope')), if(equals(variables('hubDataset'), 'Recommendations'), activity('Read Manifest').output.firstRow.exportConfig.exportName, '')),'//','/')", - "type": "Expression" - } + "name": "privatelink-blob-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" } }, { - "name": "For Each Blob", - "description": "Loop thru each exported file listed in the manifest.", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Set Destination Folder", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(variables('hasNoRows'), json('[]'), activity('Read Manifest').output.firstRow.blobs)", - "type": "Expression" - }, - "batchCount": "[if(parameters('enablePublicAccess'), 30, 4)]", - "isSequential": false, - "activities": [ - { - "name": "Execute", - "description": "Run the ingestion ETL pipeline.", - "type": "ExecutePipeline", - "dependsOn": [], - "policy": { - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_ETL_{1}', variables('safeExportContainerName'), variables('safeIngestionContainerName'))]", - "type": "PipelineReference" - }, - "waitOnCompletion": true, - "parameters": { - "blobPath": { - "value": "@item().blobName", - "type": "Expression" - }, - "destinationFolder": { - "value": "@variables('destinationFolder')", - "type": "Expression" - }, - "destinationFile": { - "value": "@last(array(split(replace(replace(item().blobName, '.gz', ''), '.csv', '.parquet'), '/')))", - "type": "Expression" - }, - "ingestionId": { - "value": "@activity('Read Manifest').output.firstRow.runInfo.runId", - "type": "Expression" - }, - "schemaFile": { - "value": "@variables('schemaFile')", - "type": "Expression" - }, - "exportDatasetType": { - "value": "@variables('exportDatasetType')", - "type": "Expression" - }, - "exportDatasetVersion": { - "value": "@variables('exportDatasetVersion')", - "type": "Expression" - } - } - } - } - ] + "name": "privatelink-table-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.table.{0}', environment().suffixes.storage))]" } }, { - "name": "Copy Manifest", - "description": "Copy the manifest to the ingestion container to trigger ADX ingestion", - "type": "Copy", - "dependsOn": [ - { - "activity": "For Each Blob", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "sink": { - "type": "JsonSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "JsonWriteSettings" - } - }, - "enableStaging": false - }, - "inputs": [ - { - "referenceName": "manifest", - "type": "DatasetReference", - "parameters": { - "fileName": "manifest.json", - "folderPath": { - "value": "@pipeline().parameters.folderPath", - "type": "Expression" - } - } - } - ], - "outputs": [ - { - "referenceName": "manifest", - "type": "DatasetReference", - "parameters": { - "fileName": "manifest.json", - "folderPath": { - "value": "[format('@concat(''{0}/'', variables(''destinationFolder''))', parameters('ingestionContainerName'))]", - "type": "Expression" - } - } - } - ] + "name": "privatelink-queue-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" + } } - ], + ] + }, + "dependsOn": [ + "appRegistration", + "dataExplorerEndpoint", + "dataExplorerPrivateDnsZone" + ] + }, + "dataFactoryVNet": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "dependsOn": [ + "appRegistration" + ] + }, + "linkedService_dataExplorer": { + "condition": "[or(variables('useAzure'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('HUB_DATA_EXPLORER'))]", + "properties": "[shallowMerge(createArray(createObject('type', 'AzureDataExplorer', 'parameters', createObject('database', createObject('type', 'String', 'defaultValue', variables('INGESTION_DB'))), 'typeProperties', createObject('endpoint', variables('dataExplorerUri'), 'database', '@{linkedService().database}', 'tenant', reference('dataFactory', '2018-06-01', 'full').identity.tenantId, 'servicePrincipalId', reference('dataFactory', '2018-06-01', 'full').identity.principalId)), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]", + "dependsOn": [ + "appRegistration", + "cluster", + "dataFactory" + ] + }, + "linkedService_ftkRepo": { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ftkRepo')]", + "properties": "[shallowMerge(createArray(createObject('type', 'HttpServer', 'parameters', createObject('filePath', createObject('type', 'string')), 'typeProperties', createObject('url', '@concat(''https://gitapp.hub.com/microsoft/finops-toolkit/'', linkedService().filePath)', 'enableServerCertificateValidation', true(), 'authenticationType', 'Anonymous')), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]", + "dependsOn": [ + "appRegistration" + ] + }, + "dataset_dataExplorer": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('HUB_DATA_EXPLORER'))]", + "properties": { + "type": "AzureDataExplorerTable", + "linkedServiceName": { + "parameters": { + "database": "@dataset().database" + }, + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference" + }, "parameters": { - "folderPath": { - "type": "string" + "database": { + "type": "String", + "defaultValue": "[variables('INGESTION_DB')]" }, + "table": { + "type": "String" + } + }, + "typeProperties": { + "table": { + "value": "@dataset().table", + "type": "Expression" + } + } + }, + "dependsOn": [ + "appRegistration", + "linkedService_dataExplorer" + ] + }, + "dataset_ftkReleaseFile": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ftkReleaseFile')]", + "properties": { + "linkedServiceName": { + "referenceName": "ftkRepo", + "type": "LinkedServiceReference" + }, + "parameters": { "fileName": { "type": "string" + }, + "version": { + "type": "string", + "defaultValue": "[variables('finOpsToolkitVersion')]" } }, - "variables": { - "date": { - "type": "String" - }, - "destinationFolder": { - "type": "String" - }, - "exportDatasetType": { - "type": "String" - }, - "exportDatasetVersion": { - "type": "String" - }, - "hasNoRows": { - "type": "Boolean" - }, - "hubDataset": { - "type": "String" - }, - "mcaColumnToCheck": { - "type": "String" - }, - "schemaFile": { - "type": "String" + "annotations": [], + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "HttpServerLocation", + "relativeUrl": { + "value": "@concat('releases/download/v', dataset().version, '/', dataset().fileName)", + "type": "Expression" + } }, - "scope": { - "type": "String" - } + "columnDelimiter": ",", + "escapeChar": "\\", + "firstRowAsHeader": true, + "quoteChar": "\"" }, - "annotations": [ - "New export" - ] + "schema": [] }, "dependsOn": [ - "dataset_config", - "dataset_manifest", - "dataset_msexports", - "dataset_msexports_gzip", - "dataset_msexports_parquet", - "pipeline_ToIngestion" - ], - "metadata": { - "description": "Queues the msexports_ETL_ingestion pipeline." - } + "appRegistration", + "linkedService_ftkRepo" + ] }, - "pipeline_ToIngestion": { + "pipeline_InitializeHub": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ETL_{1}', variables('safeExportContainerName'), variables('safeIngestionContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_InitializeHub', variables('CONFIG')))]", "properties": { "activities": [ { - "name": "Get Existing Parquet Files", - "description": "Get the previously ingested files so we can remove any older data. This is necessary to avoid data duplication in reports.", - "type": "GetMetadata", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[format('{0}_files', variables('safeIngestionContainerName'))]", - "type": "DatasetReference", - "parameters": { - "folderPath": "@pipeline().parameters.destinationFolder" - } - }, - "fieldList": [ - "childItems" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" - } - } - }, - { - "name": "Filter Out Current Exports", - "description": "Remove existing files from the current export so those files do not get deleted.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Get Existing Parquet Files", - "dependencyConditions": [ - "Completed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", - "type": "Expression" - }, - "condition": { - "value": "[format('@and(endswith(item().name, ''.parquet''), not(startswith(item().name, concat(pipeline().parameters.ingestionId, ''{0}''))))', variables('ingestionIdFileNameSeparator'))]", - "type": "Expression" - } - } - }, - { - "name": "Load Schema Mappings", - "description": "Get schema mapping file to use for the CSV to parquet conversion.", + "name": "Get Config", "type": "Lookup", "dependsOn": [], "policy": { - "timeout": "0.12:00:00", - "retry": 0, + "timeout": "0.00:05:00", + "retry": 2, "retryIntervalInSeconds": 30, "secureOutput": false, "secureInput": false @@ -14449,75 +17074,57 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@toLower(pipeline().parameters.schemaFile)", - "type": "Expression" - }, - "folderPath": "[format('{0}/schemas', parameters('configContainerName'))]" - } + "referenceName": "[variables('CONFIG')]", + "type": "DatasetReference" } } }, { - "name": "Failed to Load Schema", - "type": "Fail", + "name": "Set Version", + "type": "SetVariable", "dependsOn": [ { - "activity": "Load Schema Mappings", + "activity": "Get Config", "dependencyConditions": [ - "Failed" + "Succeeded" ] } ], "userProperties": [], "typeProperties": { - "message": { - "value": "@concat('Unable to load the ', pipeline().parameters.schemaFile, ' schema file. Please confirm the schema and version are supported for FinOps hubs ingestion. Unsupported files will remain in the msexports container.')", + "variableName": "version", + "value": { + "value": "@activity('Get Config').output.firstRow.version", "type": "Expression" - }, - "errorCode": "SchemaLoadFailed" + } } }, { - "name": "Set Additional Columns", + "name": "Set Scopes", "type": "SetVariable", "dependsOn": [ { - "activity": "Load Schema Mappings", + "activity": "Get Config", "dependencyConditions": [ "Succeeded" ] } ], - "policy": { - "secureOutput": false, - "secureInput": false - }, "userProperties": [], "typeProperties": { - "variableName": "additionalColumns", + "variableName": "scopes", "value": { - "value": "@intersection(array(json(concat('[{\"name\":\"x_SourceProvider\",\"value\":\"Microsoft\"},{\"name\":\"x_SourceName\",\"value\":\"Cost Management\"},{\"name\":\"x_SourceType\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"},{\"name\":\"x_SourceVersion\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"}'))), activity('Load Schema Mappings').output.firstRow.additionalColumns)", + "value": "@string(activity('Get Config').output.firstRow.scopes)", "type": "Expression" } } }, { - "name": "For Each Old File", - "description": "Loop thru each of the existing files from previous exports.", - "type": "ForEach", + "name": "Set Retention", + "type": "SetVariable", "dependsOn": [ { - "activity": "Convert to Parquet", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Filter Out Current Exports", + "activity": "Get Config", "dependencyConditions": [ "Succeeded" ] @@ -14525,82 +17132,31 @@ ], "userProperties": [], "typeProperties": { - "items": { - "value": "@activity('Filter Out Current Exports').output.Value", - "type": "Expression" - }, - "activities": [ - { - "name": "Delete Old Ingested File", - "description": "Delete the previously ingested files from older exports.", - "type": "Delete", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[variables('safeIngestionContainerName')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@concat(pipeline().parameters.destinationFolder, '/', item().name)", - "type": "Expression" - } - } - }, - "enableLogging": false, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - } - } - } - ] - } - }, - { - "name": "Set Destination Path", - "type": "SetVariable", - "dependsOn": [], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "destinationPath", + "variableName": "retention", "value": { - "value": "[format('@concat(pipeline().parameters.destinationFolder, ''/'', pipeline().parameters.ingestionId, ''{0}'', pipeline().parameters.destinationFile)', variables('ingestionIdFileNameSeparator'))]", + "value": "@string(activity('Get Config').output.firstRow.retention)", "type": "Expression" } } }, { - "name": "Convert to Parquet", - "description": "[format('Convert CSV to parquet and move the file to the {0} container.', parameters('ingestionContainerName'))]", - "type": "Switch", + "name": "Until Capacity Is Available", + "type": "Until", "dependsOn": [ { - "activity": "Set Destination Path", + "activity": "Set Version", "dependencyConditions": [ "Succeeded" ] }, { - "activity": "Load Schema Mappings", + "activity": "Set Scopes", "dependencyConditions": [ "Succeeded" ] }, { - "activity": "Set Additional Columns", + "activity": "Set Retention", "dependencyConditions": [ "Succeeded" ] @@ -14608,406 +17164,374 @@ ], "userProperties": [], "typeProperties": { - "on": { - "value": "@last(array(split(pipeline().parameters.blobPath, '.')))", + "expression": { + "value": "@equals(variables('tryAgain'), false)", "type": "Expression" }, - "cases": [ + "activities": [ { - "value": "csv", - "activities": [ + "name": "Confirm Ingestion Capacity", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", + "commandTimeout": "00:20:00" + }, + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[variables('INGESTION_DB')]" + } + } + }, + { + "name": "If Has Capacity", + "type": "IfCondition", + "dependsOn": [ { - "name": "Convert CSV File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:10:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "activity": "Confirm Ingestion Capacity", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", + "type": "Expression" + }, + "ifFalseActivities": [ + { + "name": "Wait for Ingestion", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 15 + } }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" + { + "name": "Try Again", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait for Ingestion", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": true + } + } + ], + "ifTrueActivities": [ + { + "name": "Set ingestion policy in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": { + "value": "[if(variables('useFabric'), format('.show database {0} policy managed_identity', variables('INGESTION_DB')), format('.alter-merge database {0} policy managed_identity \"[ {{ ''ObjectId'' : ''{1}'', ''AllowedUsages'' : ''NativeIngestion'' }}]\"', variables('INGESTION_DB'), reference('cluster', '2023-08-15', 'full').identity.principalId))]", + "type": "Expression" }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" - } + "commandTimeout": "00:20:00" }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false, - "translator": { - "value": "@activity('Load Schema Mappings').output.firstRow.translator", - "type": "Expression" + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[variables('INGESTION_DB')]" + } } }, - "inputs": [ - { - "referenceName": "[variables('safeExportContainerName')]", - "type": "DatasetReference", + { + "name": "Save Hub Settings in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Set ingestion policy in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": { + "value": "@concat('.append HubSettingsLog <| print version=\"', variables('version'), '\",scopes=dynamic(', variables('scopes'), '),retention=dynamic(', variables('retention'), ') | extend scopes = iff(isnull(scopes[0]), pack_array(scopes), scopes) | mv-apply scopeObj = scopes on (where isnotempty(scopeObj.scope) | summarize scopes = make_set(scopeObj.scope))')", + "type": "Expression" + }, + "commandTimeout": "00:20:00" + }, + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" - } + "database": "[variables('INGESTION_DB')]" } } - ], - "outputs": [ - { - "referenceName": "[variables('safeIngestionContainerName')]", - "type": "DatasetReference", + }, + { + "name": "Update PricingUnits in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Save Hub Settings in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace PricingUnits <| externaldata(x_PricingUnitDescription: string, AccountTypes: string, x_PricingBlockSize: decimal, PricingUnit: string)[@\"{0}/PricingUnits.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away AccountTypes', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" + }, + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" - } + "database": "[variables('INGESTION_DB')]" } } - ] - } - ] - }, - { - "value": "gz", - "activities": [ - { - "name": "Convert GZip CSV File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:10:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" + { + "name": "Update Regions in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update PricingUnits in ADX", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace Regions <| externaldata(ResourceLocation: string, RegionId: string, RegionName: string)[@\"{0}/Regions.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[variables('INGESTION_DB')]" } - }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false, - "translator": { - "value": "@activity('Load Schema Mappings').output.firstRow.translator", - "type": "Expression" } }, - "inputs": [ - { - "referenceName": "[format('{0}_gzip', variables('safeExportContainerName'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" - } + { + "name": "Update ResourceTypes in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update Regions in ADX", + "dependencyConditions": [ + "Succeeded" + ] } - } - ], - "outputs": [ - { - "referenceName": "[variables('safeIngestionContainerName')]", - "type": "DatasetReference", + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace ResourceTypes <| externaldata(x_ResourceType: string, SingularDisplayName: string, PluralDisplayName: string, LowerSingularDisplayName: string, LowerPluralDisplayName: string, IsPreview: bool, Description: string, IconUri: string, Links: string)[@\"{0}/ResourceTypes.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away Links', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" + }, + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" - } + "database": "[variables('INGESTION_DB')]" } } - ] - } - ] - }, - { - "value": "parquet", - "activities": [ - { - "name": "Move Parquet File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "ParquetSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" + { + "name": "Update Services in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update ResourceTypes in ADX", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" - } + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace Services <| externaldata(x_ConsumedService: string, x_ResourceType: string, ServiceName: string, ServiceCategory: string, ServiceSubcategory: string, PublisherName: string, x_PublisherCategory: string, x_Environment: string, x_ServiceModel: string)[@\"{0}/Services.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false - }, - "inputs": [ - { - "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", - "type": "DatasetReference", + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" - } + "database": "[variables('INGESTION_DB')]" } } - ], - "outputs": [ - { - "referenceName": "[variables('safeIngestionContainerName')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" - } + }, + { + "name": "Ingestion Complete", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Update Services in ADX", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false } - ] - } - ] - } - ], - "defaultActivities": [ - { - "name": "Unsupported File Type", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to ingest the specified export file because the file type is not supported. File: ', pipeline().parameters.blobPath)", - "type": "Expression" - }, - "errorCode": "UnsupportedExportFileType" + } + ] } - } - ] - } - }, - { - "name": "Read Hub Config", - "description": "Read the hub config to determine if the export should be retained.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", - "type": "DatasetReference", - "parameters": { - "fileName": "settings.json", - "folderPath": "[parameters('configContainerName')]" - } - } - } - }, - { - "name": "If Not Retaining Exports", - "description": "If the msexports retention period <= 0, delete the source file. The main reason to keep the source file is to allow for troubleshooting and reprocessing in the future.", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Convert to Parquet", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Read Hub Config", - "dependencyConditions": [ - "Completed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@lessOrEquals(coalesce(activity('Read Hub Config').output.firstRow.retention.msexports.days, 0), 0)", - "type": "Expression" - }, - "ifTrueActivities": [ { - "name": "Delete Source File", - "description": "Delete the exported data file to keep storage costs down. This file is not referenced by any reporting systems.", - "type": "Delete", - "dependsOn": [], + "name": "Abort On Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "If Has Capacity", + "dependencyConditions": [ + "Failed" + ] + } + ], "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, "secureOutput": false, "secureInput": false }, "userProperties": [], "typeProperties": { - "dataset": { - "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" - } - } - }, - "enableLogging": false, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - } + "variableName": "tryAgain", + "value": false } } - ] + ], + "timeout": "0.02:00:00" + } + }, + { + "name": "Timeout Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Until Capacity Is Available", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": "Data Explorer ingestion timed out after 2 hours while waiting for available capacity. Please re-run this pipeline to re-attempt ingestion. If you continue to see this error, please report an issue at https://aka.ms/ftk/ideas.", + "errorCode": "DataExplorerIngestionTimeout" } } ], - "parameters": { - "blobPath": { + "concurrency": 1, + "variables": { + "version": { "type": "String" }, - "destinationFile": { - "type": "string" - }, - "destinationFolder": { - "type": "string" - }, - "ingestionId": { - "type": "string" - }, - "schemaFile": { - "type": "string" - }, - "exportDatasetType": { - "type": "string" - }, - "exportDatasetVersion": { - "type": "string" - } - }, - "variables": { - "additionalColumns": { - "type": "Array" + "scopes": { + "type": "String" }, - "destinationPath": { + "retention": { "type": "String" + }, + "tryAgain": { + "type": "Boolean", + "defaultValue": true } - }, - "annotations": [] + } }, "dependsOn": [ - "dataset_config", - "dataset_ingestion", - "dataset_ingestion_files", - "dataset_msexports", - "dataset_msexports_gzip", - "dataset_msexports_parquet" + "appRegistration", + "cluster", + "linkedService_dataExplorer" ], "metadata": { - "description": "Transforms CSV data to a standard schema and converts to Parquet." + "description": "Initializes the hub instance based on the configuration settings." } }, "pipeline_ToDataExplorer": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "condition": "[or(variables('useAzure'), variables('useFabric'))]", "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ETL_dataExplorer', variables('safeIngestionContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ETL_dataExplorer', variables('INGESTION')))]", "properties": { "activities": [ { "name": "Read Hub Config", "description": "Read the hub config to determine how long data should be retained.", "type": "Lookup", - "dependsOn": [], "policy": { "timeout": "0.12:00:00", "retry": 0, @@ -15029,11 +17553,11 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", + "referenceName": "[variables('CONFIG')]", "type": "DatasetReference", "parameters": { "fileName": "settings.json", - "folderPath": "[parameters('configContainerName')]" + "folderPath": "[variables('CONFIG')]" } } } @@ -15098,7 +17622,7 @@ "commandTimeout": "00:20:00" }, "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", + "referenceName": "[variables('HUB_DATA_EXPLORER')]", "type": "LinkedServiceReference" } }, @@ -15172,10 +17696,10 @@ "commandTimeout": "00:20:00" }, "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", + "referenceName": "[variables('HUB_DATA_EXPLORER')]", "type": "LinkedServiceReference", "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "database": "[variables('INGESTION_DB')]" } } }, @@ -15200,16 +17724,16 @@ "userProperties": [], "typeProperties": { "command": { - "value": "[format('@concat(''.ingest into table '', pipeline().parameters.table, '' (\"abfss://{0}@{1}.dfs.{2}/'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.fileName, '';{3}\") with (format=\"parquet\", ingestionMappingReference=\"'', pipeline().parameters.table, ''_mapping\", tags=\"[\\\"drop-by:'', pipeline().parameters.ingestionId, ''\\\", \\\"drop-by:'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.originalFileName, ''\\\", \\\"drop-by:ftk-version-{4}\\\"]\"); print Success = assert(iff(toscalar($command_results | project-keep HasErrors) == false, true, false), \"Ingestion Failed\")'')', parameters('ingestionContainerName'), parameters('storageAccountName'), environment().suffixes.storage, if(variables('useFabric'), 'impersonate', 'managed_identity=system'), variables('ftkVersion'))]", + "value": "[format('@concat(''.ingest into table '', pipeline().parameters.table, '' (\"abfss://{0}@{1}.dfs.{2}/'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.fileName, '';{3}\") with (format=\"parquet\", ingestionMappingReference=\"'', pipeline().parameters.table, ''_mapping\", tags=\"[\\\"drop-by:'', pipeline().parameters.ingestionId, ''\\\", \\\"drop-by:'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.originalFileName, ''\\\", \\\"drop-by:ftk-version-{4}\\\"]\"); print Success = assert(iff(toscalar($command_results | project-keep HasErrors) == false, true, false), \"Ingestion Failed\")'')', variables('INGESTION'), parameters('app').storage, environment().suffixes.storage, if(variables('useFabric'), 'impersonate', 'managed_identity=system'), variables('finOpsToolkitVersion'))]", "type": "Expression" }, "commandTimeout": "01:00:00" }, "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", + "referenceName": "[variables('HUB_DATA_EXPLORER')]", "type": "LinkedServiceReference", "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "database": "[variables('INGESTION_DB')]" } } }, @@ -15240,10 +17764,10 @@ "commandTimeout": "00:20:00" }, "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", + "referenceName": "[variables('HUB_DATA_EXPLORER')]", "type": "LinkedServiceReference", "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "database": "[variables('INGESTION_DB')]" } } }, @@ -15309,648 +17833,1996 @@ "errorCode": "DataExplorerIngestionFailed" } }, - { - "name": "Abort On Pre-Ingest Drop Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Pre-Ingest Cleanup", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } + { + "name": "Abort On Pre-Ingest Drop Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Pre-Ingest Cleanup", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false + } + }, + { + "name": "Pre-Ingest Drop Failed Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Abort On Pre-Ingest Drop Error", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Data Explorer pre-ingestion cleanup (drop extents from raw table) for the ', pipeline().parameters.table, ' table failed. Ingestion was not completed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", + "type": "Expression" + }, + "errorCode": "DataExplorerPreIngestionDropFailed" + } + }, + { + "name": "Abort On Post-Ingest Drop Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Post-Ingest Cleanup", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false + } + }, + { + "name": "Post-Ingest Drop Failed Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Abort On Post-Ingest Drop Error", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Data Explorer post-ingestion cleanup (drop extents from final tables) for the ', replace(pipeline().parameters.table, '_raw', '_final_*'), ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", + "type": "Expression" + }, + "errorCode": "DataExplorerPostIngestionDropFailed" + } + } + ] + } + } + ], + "timeout": "0.02:00:00" + } + } + ], + "parameters": { + "folderPath": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "originalFileName": { + "type": "string" + }, + "ingestionId": { + "type": "string" + }, + "table": { + "type": "string" + } + }, + "variables": { + "tryAgain": { + "type": "Boolean", + "defaultValue": true + }, + "logRetentionDays": { + "type": "Integer", + "defaultValue": 0 + }, + "finalRetentionMonths": { + "type": "Integer", + "defaultValue": 999 + } + }, + "annotations": [] + }, + "dependsOn": [ + "appRegistration", + "linkedService_dataExplorer" + ], + "metadata": { + "description": "Ingests parquet data into an Azure Data Explorer cluster." + } + }, + "pipeline_ExecuteIngestionETL": { + "condition": "[or(variables('useAzure'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ExecuteETL', variables('INGESTION')))]", + "properties": { + "concurrency": 1, + "activities": [ + { + "name": "Wait", + "description": "Files may not be available immediately after being created.", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 60 + } + }, + { + "name": "Set Container Folder Path", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "containerFolderPath", + "value": { + "value": "@join(skip(array(split(pipeline().parameters.folderPath, '/')), 1), '/')", + "type": "Expression" + } + } + }, + { + "name": "Get Existing Parquet Files", + "description": "Get the previously ingested files so we can get file paths.", + "type": "GetMetadata", + "dependsOn": [ + { + "activity": "Set Container Folder Path", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "ingestion_files", + "type": "DatasetReference", + "parameters": { + "folderPath": "@variables('containerFolderPath')" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + } + }, + { + "name": "Filter Out Folders", + "description": "Remove any folders or manifest files.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Get Existing Parquet Files", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", + "type": "Expression" + }, + "condition": { + "value": "@and(equals(item().type, 'File'), not(contains(toLower(item().name), 'manifest.json')))", + "type": "Expression" + } + } + }, + { + "name": "Set Ingestion Timestamp", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "timestamp", + "value": { + "value": "@utcNow()", + "type": "Expression" + } + } + }, + { + "name": "For Each Old File", + "description": "Loop thru each of the existing files.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Filter Out Folders", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Ingestion Timestamp", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "batchCount": "[variables('dataExplorerIngestionCapacity')]", + "items": { + "value": "@activity('Filter Out Folders').output.Value", + "type": "Expression" + }, + "activities": [ + { + "name": "Execute", + "description": "Run the ADX ETL pipeline.", + "type": "ExecutePipeline", + "dependsOn": [], + "policy": { + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_ETL_dataExplorer', variables('INGESTION'))]", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "folderPath": { + "value": "@variables('containerFolderPath')", + "type": "Expression" + }, + "fileName": { + "value": "@item().name", + "type": "Expression" }, - { - "name": "Pre-Ingest Drop Failed Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Abort On Pre-Ingest Drop Error", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Data Explorer pre-ingestion cleanup (drop extents from raw table) for the ', pipeline().parameters.table, ' table failed. Ingestion was not completed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", - "type": "Expression" - }, - "errorCode": "DataExplorerPreIngestionDropFailed" - } + "originalFileName": { + "value": "[format('@last(array(split(item().name, ''{0}'')))', variables('INGESTION_ID_SEPARATOR'))]", + "type": "Expression" }, - { - "name": "Abort On Post-Ingest Drop Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Post-Ingest Cleanup", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } + "ingestionId": { + "value": "[format('@concat(first(array(split(item().name, ''{0}''))), ''_'', variables(''timestamp''))', variables('INGESTION_ID_SEPARATOR'))]", + "type": "Expression" }, - { - "name": "Post-Ingest Drop Failed Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Abort On Post-Ingest Drop Error", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Data Explorer post-ingestion cleanup (drop extents from final tables) for the ', replace(pipeline().parameters.table, '_raw', '_final_*'), ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", - "type": "Expression" - }, - "errorCode": "DataExplorerPostIngestionDropFailed" - } + "table": { + "value": "@concat(first(array(split(variables('containerFolderPath'), '/'))), '_raw')", + "type": "Expression" } - ] + } + } + } + ] + } + }, + { + "name": "If No Files", + "description": "If there are no files found, fail the pipeline.", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Filter Out Folders", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@equals(length(activity('Filter Out Folders').output.Value), 0)", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Files Not Found", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to locate parquet files to ingest from the ', pipeline().parameters.folderPath, ' path. Please confirm the folder path is the full path, including the \"ingestion\" container and not starting with or ending with a slash (\"/\").')", + "type": "Expression" + }, + "errorCode": "IngestionFilesNotFound" + } + } + ] + } + } + ], + "parameters": { + "folderPath": { + "type": "string" + } + }, + "variables": { + "containerFolderPath": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + }, + "annotations": [ + "New ingestion" + ] + }, + "dependsOn": [ + "appRegistration", + "pipeline_ToDataExplorer" + ], + "metadata": { + "description": "Queues the ingestion_ETL_dataExplorer pipeline to account for Data Factory pipeline trigger limits." + } + }, + "appRegistration": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_Register", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "version": { + "value": "[variables('finOpsToolkitVersion')]" + }, + "features": { + "value": [ + "DataFactory", + "Storage" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "5436870138046688593" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } } } - ], - "timeout": "0.02:00:00" + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } - } - ], - "parameters": { - "folderPath": { - "type": "string" - }, - "fileName": { - "type": "string" - }, - "originalFileName": { - "type": "string" - }, - "ingestionId": { - "type": "string" - }, - "table": { - "type": "string" - } - }, - "variables": { - "tryAgain": { - "type": "Boolean", - "defaultValue": true - }, - "logRetentionDays": { - "type": "Integer", - "defaultValue": 0 }, - "finalRetentionMonths": { - "type": "Integer", - "defaultValue": 999 - } - }, - "annotations": [] - }, - "dependsOn": [ - "dataset_config", - "linkedService_dataExplorer" - ], - "metadata": { - "description": "Ingests parquet data into an Azure Data Explorer cluster." - } - }, - "pipeline_ExecuteIngestionETL": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ExecuteETL', variables('safeIngestionContainerName')))]", - "properties": { - "concurrency": 1, - "activities": [ - { - "name": "Wait", - "description": "Files may not be available immediately after being created.", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 60 + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + }, + "version": { + "type": "string", + "metadata": { + "description": "Required. Version number of the FinOps hub app." + } + }, + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." + } + }, + "storageRoles": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + } + }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } } }, - { - "name": "Set Container Folder Path", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Succeeded" + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0}', parameters('app').id)]", + "version": "[parameters('version')]" + } + }, + "resources": [] + } + }, + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", + "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + }, + "resources": { + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", + "properties": { + "name": "[parameters('app').storage]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "storageAccount" + ] + }, + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", + "properties": { + "name": "[parameters('app').keyVault]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "keyVault" + ] + }, + "dataFactory::managedVirtualNetwork": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "properties": {}, + "dependsOn": [ + "dataFactory" + ] + }, + "dataFactory::managedIntegrationRuntime": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "default", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('app').hub.location]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedVirtualNetwork" + ] + }, + "dataFactory::linkedService_keyVault": { + "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "keyVault" + ] + }, + "dataFactory::linkedService_storageAccount": { + "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "storageAccount" + ] + }, + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false + }, + "dependsOn": [ + "dfsEndpoint" + ] }, - "userProperties": [], - "typeProperties": { - "variableName": "containerFolderPath", - "value": { - "value": "@join(skip(array(split(pipeline().parameters.folderPath, '/')), 1), '/')", - "type": "Expression" + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('app').hub.options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } } - } - }, - { - "name": "Get Existing Parquet Files", - "description": "Get the previously ingested files so we can get file paths.", - "type": "GetMetadata", - "dependsOn": [ - { - "activity": "Set Container Folder Path", - "dependencyConditions": [ - "Succeeded" + }, + "storageRoleAssignments": { + "copy": { + "name": "storageRoleAssignments", + "count": "[length(variables('factoryStorageRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "storageAccount" + ] + }, + "triggerManagerIdentity": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "dependsOn": [ + "dataFactory" + ] + }, + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "triggerManagerIdentity" + ] + }, + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]", + "location": "[parameters('app').hub.location]", + "sku": { + "name": "[parameters('app').hub.options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" + }, + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + }, + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "blob" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('app').hub.options.keyVaultSku]", + "family": "A" + }, + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + } + }, + "dependsOn": [ + "dataFactory" + ] + }, + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('app').keyVault)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.keyVault]" + }, + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + }, + "dependsOn": [ + "keyVault" + ] }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[format('{0}_files', variables('safeIngestionContainerName'))]", - "type": "DatasetReference", + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", "parameters": { - "folderPath": "@variables('containerFolderPath')" + "keyVaultName": { + "value": "[parameters('app').keyVault]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } } }, - "fieldList": [ - "childItems" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" - } - } - }, - { - "name": "Filter Out Folders", - "description": "Remove any folders or manifest files.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Get Existing Parquet Files", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", - "type": "Expression" + "dependsOn": [ + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", + "getStoragePrivateEndpointConnections", + "keyVault" + ] + }, + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } }, - "condition": { - "value": "@and(equals(item().type, 'File'), not(contains(toLower(item().name), 'manifest.json')))", - "type": "Expression" - } - } - }, - { - "name": "Set Ingestion Timestamp", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections", + "keyVault" + ] }, - "userProperties": [], - "typeProperties": { - "variableName": "timestamp", - "value": { - "value": "@utcNow()", - "type": "Expression" - } - } - }, - { - "name": "For Each Old File", - "description": "Loop thru each of the existing files.", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Filter Out Folders", - "dependencyConditions": [ - "Succeeded" - ] + "getStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } }, - { - "activity": "Data Explorer validation", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "batchCount": "[parameters('dataExplorerIngestionCapacity')]", - "items": { - "value": "@activity('Filter Out Folders').output.Value", - "type": "Expression" + "dependsOn": [ + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", + "stopTriggers", + "storageAccount" + ] + }, + "approveStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } }, - "activities": [ - { - "name": "Execute", - "description": "Run the ADX ETL pipeline.", - "type": "ExecutePipeline", - "dependsOn": [], - "policy": { - "secureInput": false + "dependsOn": [ + "getStoragePrivateEndpointConnections", + "storageAccount" + ] + }, + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" }, - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_ETL_dataExplorer', variables('safeIngestionContainerName'))]", - "type": "PipelineReference" + "identityName": { + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('app').dataFactory]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } }, - "waitOnCompletion": true, - "parameters": { - "folderPath": { - "value": "@variables('containerFolderPath')", - "type": "Expression" + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } }, - "fileName": { - "value": "@item().name", - "type": "Expression" + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } }, - "originalFileName": { - "value": "[format('@last(array(split(item().name, ''{0}'')))', variables('ingestionIdFileNameSeparator'))]", - "type": "Expression" + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } }, - "ingestionId": { - "value": "[format('@concat(first(array(split(item().name, ''{0}''))), ''_'', variables(''timestamp''))', variables('ingestionIdFileNameSeparator'))]", - "type": "Expression" + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } }, - "table": { - "value": "@concat(first(array(split(variables('containerFolderPath'), '/'))), '_raw')", - "type": "Expression" + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } } - } - } - ] - } - }, - { - "name": "If No Files", - "description": "If there are no files found, fail the pipeline.", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Filter Out Folders", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@equals(length(activity('Filter Out Folders').output.Value), 0)", - "type": "Expression" - }, - "ifTrueActivities": [ - { - "name": "Files Not Found", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to locate parquet files to ingest from the ', pipeline().parameters.folderPath, ' path. Please confirm the folder path is the full path, including the \"ingestion\" container and not starting with or ending with a slash (\"/\").')", - "type": "Expression" - }, - "errorCode": "IngestionFilesNotFound" - } - } - ] - } - }, - { - "name": "Data Explorer validation", - "description": "If Data Explorer is stopped, start it", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Set Ingestion Timestamp", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "[format('@equals({0}, true)', variables('deployDataExplorer'))]", - "type": "Expression" - }, - "ifTrueActivities": [ - { - "name": "Start ADX Cluster", - "type": "WebActivity", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "method": "POST", - "url": { - "value": "[format('{0}{1}/start?api-version=2024-04-13', environment().resourceManager, resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')))]", - "type": "Expression" + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - "body": "{}", - "authentication": { - "type": "MSI", - "resource": { - "value": "[environment().resourceManager]", - "type": "Expression" + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." } - } - } - }, - { - "name": "Error ADX Start", - "type": "Fail", - "dependsOn": [ - { - "activity": "Start ADX Cluster After Error", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Failed to start the Data Explorer instance. Message: ', activity('Start ADX Cluster After Error').output.error.message)", - "type": "Expression" }, - "errorCode": { - "value": "@activity('Start ADX Cluster After Error').output.error.code", - "type": "Expression" - } - } - }, - { - "name": "Wait ADX Provision State", - "type": "Wait", - "dependsOn": [ - { - "activity": "Start ADX Cluster", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 600 - } - }, - { - "name": "Start ADX Cluster After Error", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Wait ADX Provision State", - "dependencyConditions": [ - "Succeeded" - ] + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "method": "POST", - "url": { - "value": "[format('{0}{1}/start?api-version=2024-04-13', environment().resourceManager, resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')))]", - "type": "Expression", - "body": "{}" + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" }, - "authentication": { - "type": "MSI", - "resource": { - "value": "[environment().resourceManager]", - "type": "Expression" - } + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } + }, + "dependsOn": [ + "appTelemetry", + "dataFactory", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" ] } - } - ], - "parameters": { - "folderPath": { - "type": "string" - } - }, - "variables": { - "containerFolderPath": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - }, - "annotations": [ - "New ingestion" - ] - }, - "dependsOn": [ - "dataset_ingestion_files", - "pipeline_ToDataExplorer" - ], - "metadata": { - "description": "Queues the ingestion_ETL_dataExplorer pipeline to account for Data Factory pipeline trigger limits." - } - }, - "azuretimezones": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "azuretimezones", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('location')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "4022825617953122148" - } - }, - "parameters": { - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." - } - }, - "timezoneobject": { - "type": "object", - "defaultValue": { - "australiaeast": "AUS Eastern Standard Time", - "australiacentral": "AUS Eastern Standard Time", - "australiacentral2": "AUS Eastern Standard Time", - "australiasoutheast": "AUS Eastern Standard Time", - "brazilsouth": "E. South America Standard Time", - "canadacentral": "Central Standard Time", - "canadaeast": "Eastern Standard Time", - "centralindia": "India Standard Time", - "centralus": "Central Standard Time", - "eastasia": "China Standard Time", - "eastus": "Eastern Standard Time", - "eastus2": "Eastern Standard Time", - "francecentral": "W. Europe Standard Time", - "germanynorth": "W. Europe Standard Time", - "germanywestcentral": "W. Europe Standard Time", - "japaneast": "Japan Standard Time", - "japanwest": "Japan Standard Time", - "koreacentral": "Korea Standard Time", - "koreasouth": "Korea Standard Time", - "northcentralus": "Central Standard Time", - "northeurope": "GMT Standard Time", - "norwayeast": "W. Europe Standard Time", - "norwaywest": "W. Europe Standard Time", - "southcentralus": "Central Standard Time", - "southindia": "India Standard Time", - "southeastasia": "Singapore Standard Time", - "switzerlandnorth": "W. Europe Standard Time", - "switzerlandwest": "W. Europe Standard Time", - "uksouth": "GMT Standard Time", - "ukwest": "GMT Standard Time", - "westcentralus": "Central Standard Time", - "westeurope": "W. Europe Standard Time", - "westindia": "India Standard Time", - "westus": "Pacific Standard Time", - "westus2": "Pacific Standard Time" - } - }, - "utchrs": { - "type": "string", - "defaultValue": "[utcNow('hh')]" - }, - "utcmins": { - "type": "string", - "defaultValue": "[utcNow('mm')]" - }, - "utcsecs": { - "type": "string", - "defaultValue": "[utcNow('ss')]" - } - }, - "variables": { - "loc": "[toLower(replace(parameters('location'), ' ', ''))]", - "timezone": "[coalesce(tryGet(parameters('timezoneobject'), variables('loc')), 'Universal Coordinated Time')]" }, - "resources": [], "outputs": { - "AzureRegion": { + "dataFactoryId": { "type": "string", - "value": "[parameters('location')]" + "metadata": { + "description": "Resource ID of the Data Factory instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" }, - "Timezone": { + "keyVaultId": { "type": "string", - "value": "[variables('timezone')]" + "metadata": { + "description": "Resource ID of the Key Vault instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" }, - "UtcHours": { + "storageAccountId": { "type": "string", - "value": "[parameters('utchrs')]" + "metadata": { + "description": "Resource ID of the storage account instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, - "UtcMinutes": { + "principalId": { "type": "string", - "value": "[parameters('utcmins')]" + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" }, - "UtcSeconds": { + "triggerManagerIdentityName": { "type": "string", - "value": "[parameters('utcsecs')]" + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" } } } } }, - "getStoragePrivateEndpointConnections": { - "condition": "[not(parameters('enablePublicAccess'))]", + "ingestion_OpenDataInternalScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "GetStoragePrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionOpenDataInternal", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "storageAccountName": { - "value": "[parameters('storageAccountName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" + }, + "databaseName": { + "value": "[variables('INGESTION_DB')]" + }, + "scripts": { + "value": { + "OpenDataFunctions_resource_type_1": "[variables('$fxv#0')]", + "OpenDataFunctions_resource_type_2": "[variables('$fxv#1')]", + "OpenDataFunctions_resource_type_3": "[variables('$fxv#2')]", + "OpenDataFunctions_resource_type_4": "[variables('$fxv#3')]", + "OpenDataFunctions_resource_type_5": "[variables('$fxv#4')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -15959,72 +19831,97 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "491732910990436410" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. Name of the FinOps hub Data Explorer instance." } }, - "storageAccountName": { + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { "type": "string", + "defaultValue": "[utcNow()]", "metadata": { - "description": "Required. Name of the storage account." + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "storageManagedPrivateEndpoint" + "cluster", + "cluster::ingestionDb" ] }, - "approveStoragePrivateEndpointConnections": { - "condition": "[not(parameters('enablePublicAccess'))]", + "ingestion_InitScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ApproveStoragePrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionInit", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "storageAccountName": { - "value": "[parameters('storageAccountName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + "databaseName": { + "value": "[variables('INGESTION_DB')]" + }, + "scripts": { + "value": { + "openData": "[variables('$fxv#5')]", + "common": "[variables('$fxv#6')]", + "infra": "[variables('$fxv#7')]", + "rawTables": "[replace(variables('$fxv#8'), '$$rawRetentionInDays$$', string(parameters('rawRetentionInDays')))]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -16033,69 +19930,96 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "491732910990436410" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer instance." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." } }, - "storageAccountName": { + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { "type": "string", + "defaultValue": "[utcNow()]", "metadata": { - "description": "Required. Name of the storage account." + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "getStoragePrivateEndpointConnections" + "cluster", + "cluster::ingestionDb", + "ingestion_OpenDataInternalScripts" ] }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", + "ingestion_VersionedScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "GetKeyVaultPrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionVersioned", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "keyVaultName": { - "value": "[parameters('keyVaultName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" + }, + "databaseName": { + "value": "[variables('INGESTION_DB')]" + }, + "scripts": { + "value": { + "v1_0": "[variables('$fxv#9')]", + "v1_2": "[variables('$fxv#10')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -16104,71 +20028,96 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "11127712826844297340" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. Name of the FinOps hub Data Explorer instance." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." } }, - "keyVaultName": { + "forceUpdateTag": { "type": "string", + "defaultValue": "[utcNow()]", "metadata": { - "description": "Required. Name of the KeyVault." + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "keyVaultManagedPrivateEndpoint" + "cluster", + "cluster::ingestionDb", + "ingestion_InitScripts" ] }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", + "hub_InitScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubInit", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "keyVaultName": { - "value": "[parameters('keyVaultName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + "databaseName": { + "value": "[variables('HUB_DB')]" + }, + "scripts": { + "value": { + "common": "[variables('$fxv#11')]", + "openData": "[variables('$fxv#12')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -16177,68 +20126,96 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "11127712826844297340" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. Name of the FinOps hub Data Explorer instance." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." } }, - "keyVaultName": { + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { "type": "string", + "defaultValue": "[utcNow()]", "metadata": { - "description": "Required. Name of the KeyVault." + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "getKeyVaultPrivateEndpointConnections" + "cluster", + "cluster::hubDb", + "ingestion_InitScripts" ] }, - "getDataExplorerPrivateEndpointConnections": { - "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", + "hub_VersionedScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "GetDataExplorerPrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubVersioned", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "dataExplorerName": { - "value": "[parameters('dataExplorerName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" + }, + "databaseName": { + "value": "[variables('HUB_DB')]" + }, + "scripts": { + "value": { + "v1_0": "[variables('$fxv#13')]", + "v1_2": "[variables('$fxv#14')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -16247,71 +20224,96 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9394304748737938982" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. Name of the FinOps hub Data Explorer instance." } }, - "dataExplorerName": { + "databaseName": { "type": "string", "metadata": { - "description": "Required. Name of the ADX cluster." + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "type": "Microsoft.Kusto/clusters/databases/scripts", "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "dataExplorerManagedPrivateEndpoint" + "cluster", + "cluster::hubDb", + "hub_InitScripts", + "ingestion_VersionedScripts" ] }, - "approveDataExplorerPrivateEndpointConnections": { - "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", + "hub_LatestScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ApproveDataExplorerPrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubLatest", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "dataExplorerName": { - "value": "[parameters('dataExplorerName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" + }, + "databaseName": { + "value": "[variables('HUB_DB')]" + }, + "scripts": { + "value": { + "latest": "[variables('$fxv#15')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" }, - "privateEndpointConnections": { - "value": "[reference('getDataExplorerPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -16320,465 +20322,353 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9394304748737938982" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. Name of the FinOps hub Data Explorer instance." } }, - "dataExplorerName": { + "databaseName": { "type": "string", "metadata": { - "description": "Required. Name of the ADX cluster." + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "type": "Microsoft.Kusto/clusters/databases/scripts", "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "getDataExplorerPrivateEndpointConnections" + "cluster", + "cluster::hubDb", + "hub_VersionedScripts" ] }, - "deleteOldResources": { + "getDataExplorerPrivateEndpointConnections": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_ADF.DeleteOldResources", + "apiVersion": "2025-04-01", + "name": "GetDataExplorerPrivateEndpointConnections", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('dataFactoryName')]" - } - ] + "dataExplorerName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "18030646605933559953" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, + "dataExplorerName": { + "type": "string", "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the ADX cluster." } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" } } } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "cluster", + "dataFactoryVNet::dataExplorerManagedPrivateEndpoint" + ] + }, + "approveDataExplorerPrivateEndpointConnections": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveDataExplorerPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataExplorerName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" + }, + "privateEndpointConnections": { + "value": "[reference('getDataExplorerPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "18030646605933559953" + } }, "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "identityName": { + "dataExplorerName": { "type": "string", "metadata": { - "description": "Required. Name of the managed identity to create." + "description": "Required. Name of the ADX cluster." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "cluster", + "getDataExplorerPrivateEndpointConnections" + ] + }, + "trigger_IngestionManifestAdded": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_IngestionManifestAddedTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('app').dataFactory]" + }, + "triggerName": { + "value": "[format('{0}_ManifestAdded', variables('INGESTION'))]" + }, + "pipelineName": { + "value": "[format('{0}_ExecuteETL', variables('INGESTION'))]" + }, + "pipelineParameters": { + "value": { + "folderPath": "@triggerBody().folderPath" + } + }, + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "storageContainer": { + "value": "[variables('INGESTION')]" + }, + "storagePathEndsWith": { + "value": "manifest.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14264521107451792604" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." } }, - "scriptName": { + "triggerName": { "type": "string", - "defaultValue": "[deployment().name]", "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + "description": "Required. Name of the Data Factory trigger to create or update." } }, - "scriptContent": { + "storageAccountName": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Optional. Azure storage container to monitor for updates and trigger events for." } }, - "arguments": { + "storageContainer": { "type": "string", "defaultValue": "", "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." + "description": "Optional. Azure storage container to monitor for updates and trigger events for." } }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], + "storagePathStartsWith": { + "type": "string", + "defaultValue": "", "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "storagePathEndsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] + "pipelineName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + } + } + }, + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" + }, + "parameters": "[parameters('pipelineParameters')]" + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + } } - } + ] } }, "dependsOn": [ - "stopTriggers", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" + "appRegistration", + "pipeline_ExecuteIngestionETL" ] }, - "stopTriggers": { + "runInitializationPipeline": { + "condition": "[or(variables('useAzure'), variables('useFabric'))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_ADF.StopTriggers", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_InitializeHub", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -16788,33 +20678,17 @@ "app": { "value": "[parameters('app')]" }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" - }, - "scriptContent": { - "value": "[variables('$fxv#1')]" + "dataFactoryInstances": { + "value": [ + "[parameters('app').dataFactory]" + ] }, - "arguments": { - "value": "-Stop" + "identityName": { + "value": "[reference('appRegistration').outputs.triggerManagerIdentityName.value]" }, - "environmentVariables": { + "startPipelines": { "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('dataFactoryName')]" - }, - { - "name": "Triggers", - "value": "[join(variables('allHubTriggers'), '|')]" - } + "[format('{0}_InitializeHub', variables('CONFIG'))]" ] } }, @@ -16825,22 +20699,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" + "version": "0.39.26.7824", + "templateHash": "4749940909471549408" } }, "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "_1.HubProperties": { "type": "object", "properties": { @@ -16901,702 +20764,1090 @@ } }, "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" } } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } }, - "_1.HubRoutingProperties": { - "type": "object", + "dataFactoryInstances": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of Azure Data Factory instances to start triggers for. Can be up to 1 per publisher." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to use when starting the triggers." + } + }, + "startAllTriggers": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Start all triggers for the Data Factory instances. Default: false." + } + }, + "startPipelines": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. List of pipelines to run. Default: [] (no pipelines)." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "uniqueInstances": "[union(filter(parameters('dataFactoryInstances'), lambda('adf', not(empty(lambdaVariables('adf'))))), createArray())]" + }, + "resources": { + "initialize": { + "copy": { + "name": "initialize", + "count": "[length(variables('uniqueInstances'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[if(lessOrEquals(length(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()])), 64), format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), substring(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), 0, 64))]", "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "scriptStorage": { - "type": "string" + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[parameters('identityName')]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[variables('uniqueInstances')[copyIndex()]]" + }, + { + "name": "Pipelines", + "value": "[join(parameters('startPipelines'), '|')]" + }, + { + "name": "StartAllTriggers", + "value": "[string(parameters('startAllTriggers'))]" + } + ] + } }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - "keyVault": { - "type": "string" + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } }, - "scripts": { - "type": "string" + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } }, - "storage": { - "type": "string" + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" }, - "displayName": { - "type": "string" + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" }, - "suffix": { - "type": "string" + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] }, - "tags": { - "type": "object" + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" } } } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } } } }, "dependsOn": [ - "triggerManagerIdentity", - "triggerManagerRoleAssignments" + "appRegistration", + "pipeline_InitializeHub" ] + } + }, + "outputs": { + "clusterId": { + "type": "string", + "metadata": { + "description": "The resource ID of the cluster." + }, + "value": "[if(variables('useFabric'), '', resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-')))]" }, - "trigger_ExportManifestAdded": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_ExportManifestAddedTrigger", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('dataFactoryName')]" - }, - "triggerName": { - "value": "[variables('exportManifestAddedTriggerName')]" - }, - "pipelineName": { - "value": "[format('{0}_ExecuteETL', variables('safeExportContainerName'))]" - }, - "pipelineParameters": { - "value": { - "folderPath": "@triggerBody().folderPath", - "fileName": "@triggerBody().fileName" - } - }, - "storageAccountName": { - "value": "[parameters('storageAccountName')]" - }, - "storageContainer": { - "value": "[parameters('exportContainerName')]" - }, - "storagePathEndsWith": { - "value": "manifest.json" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "10717799137710795976" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } - }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." - } - }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." - } - }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." - } - }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." - } - }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." - } - } - }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" - }, - "parameters": "[parameters('pipelineParameters')]" - } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] - } - } - } - ] - } + "principalId": { + "type": "string", + "metadata": { + "description": "The ID of the cluster system assigned managed identity." }, - "dependsOn": [ - "pipeline_ExecuteExportsETL", - "stopTriggers" - ] + "value": "[if(variables('useFabric'), '', reference('cluster', '2023-08-15', 'full').identity.principalId)]" }, - "trigger_IngestionManifestAdded": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_IngestionManifestAddedTrigger", + "clusterName": { + "type": "string", + "metadata": { + "description": "The name of the cluster." + }, + "value": "[if(variables('useFabric'), '', replace(parameters('clusterName'), '_', '-'))]" + }, + "clusterUri": { + "type": "string", + "metadata": { + "description": "The URI of the cluster." + }, + "value": "[variables('dataExplorerUri')]" + }, + "ingestionDbName": { + "type": "string", + "metadata": { + "description": "The name of the database for data ingestion." + }, + "value": "[variables('INGESTION_DB')]" + }, + "hubDbName": { + "type": "string", + "metadata": { + "description": "The name of the database for queries." + }, + "value": "[variables('HUB_DB')]" + }, + "clusterIngestionCapacity": { + "type": "int", + "metadata": { + "description": "Max ingestion capacity of the cluster." + }, + "value": "[variables('dataExplorerIngestionCapacity')]" + } + } + } + }, + "dependsOn": [ + "cmExports", + "core", + "deleteOldResources" + ] + }, + "remoteHub": { + "condition": "[not(empty(parameters('remoteHubStorageKey')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.RemoteHub", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'RemoteHub')]" + }, + "remoteStorageKey": { + "value": "[parameters('remoteHubStorageKey')]" + }, + "remoteHubStorageUri": { + "value": "[parameters('remoteHubStorageUri')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "3199707033377872229" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('dataFactoryName')]" - }, - "triggerName": { - "value": "[variables('ingestionManifestAddedTriggerName')]" - }, - "pipelineName": { - "value": "[format('{0}_ExecuteETL', variables('safeIngestionContainerName'))]" - }, - "pipelineParameters": { - "value": { - "folderPath": "@triggerBody().folderPath" - } - }, - "storageAccountName": { - "value": "[parameters('storageAccountName')]" - }, - "storageContainer": { - "value": "[parameters('ingestionContainerName')]" - }, - "storagePathEndsWith": { - "value": "manifest.json" - } + "name": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "10717799137710795976" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } - }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." - } + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } + "keyVaultSku": { + "type": "string" }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } + "networkAddressPrefix": { + "type": "string" }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." - } + "privateRouting": { + "type": "bool" }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." - } + "publisherIsolation": { + "type": "bool" }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." - } + "storageInfrastructureEncryption": { + "type": "bool" }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." - } + "storageSku": { + "type": "string" } - }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" - }, - "parameters": "[parameters('pipelineParameters')]" - } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] - } - } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } - ] + } } }, - "dependsOn": [ - "pipeline_ExecuteIngestionETL", - "stopTriggers" - ] + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } }, - "trigger_SettingsUpdated": { - "condition": "[parameters('enableManagedExports')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_SettingsUpdatedTrigger", + "_1.HubRoutingProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "networkId": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('dataFactoryName')]" - }, - "triggerName": { - "value": "[variables('updateConfigTriggerName')]" - }, - "pipelineName": { - "value": "[format('{0}_ConfigureExports', variables('safeConfigContainerName'))]" - }, - "pipelineParameters": { - "value": {} - }, - "storageAccountName": { - "value": "[parameters('storageAccountName')]" - }, - "storageContainer": { - "value": "[parameters('configContainerName')]" - }, - "storagePathEndsWith": { - "value": "settings.json" - } + "networkName": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "10717799137710795976" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } + "queue": { + "$ref": "#/definitions/_1.IdNameObject" }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." - } + "dataFactory": { + "type": "string" }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." - } + "keyVault": { + "type": "string" }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." - } + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "privateRoutingForLinkedServices": { + "parameters": [ + { + "$ref": "#/definitions/_1.HubProperties", + "name": "hub" + } + ], + "output": { + "type": "object", + "value": "[if(parameters('hub').options.privateRouting, createObject('connectVia', createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference')), createObject())]" + }, + "metadata": { + "description": "Returns an object that represents the properties needed to enable private routing for linked services. Use property expansion (`...value`) to apply to a linkedServices resource.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + }, + "remoteStorageKey": { + "type": "securestring", + "metadata": { + "description": "Required. Create and store a key for a remote storage account." + } + }, + "remoteHubStorageUri": { + "type": "string", + "metadata": { + "description": "Required. Remote storage account for ingestion dataset." + } + }, + "ingestionContainerName": { + "type": "string", + "defaultValue": "ingestion", + "metadata": { + "description": "Optional. Name of the ingestion container. Default: ingestion." + } + } + }, + "variables": { + "storageKeySecretName": "[format('{0}-storage-key', toLower(parameters('app').hub.name))]", + "finOpsToolkitVersion": "12.0" + }, + "resources": { + "dataFactory::linkedService_remoteHubStorage": { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'remoteHubStorage')]", + "properties": "[shallowMerge(createArray(createObject('annotations', createArray(), 'parameters', createObject(), 'type', 'AzureBlobFS', 'typeProperties', createObject('url', parameters('remoteHubStorageUri'), 'accountKey', createObject('type', 'AzureKeyVaultSecret', 'store', createObject('referenceName', parameters('app').keyVault, 'type', 'LinkedServiceReference'), 'secretName', variables('storageKeySecretName')))), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]" + }, + "dataFactory::dataset_ingestion": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('ingestionContainerName'))]", + "properties": { + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." - } - } - }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" - }, - "parameters": "[parameters('pipelineParameters')]" - } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] - } - } + "fileSystem": "[parameters('ingestionContainerName')]" + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "remoteHubStorage", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "dataFactory::linkedService_remoteHubStorage" + ] + }, + "dataFactory::dataset_ingestion_files": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', parameters('ingestionContainerName')))]", + "properties": { + "annotations": [], + "parameters": { + "folderPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileSystem": "[parameters('ingestionContainerName')]", + "folderPath": { + "value": "@dataset().folderPath", + "type": "Expression" } - ] + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "remoteHubStorage", + "type": "LinkedServiceReference" } }, "dependsOn": [ - "pipeline_ConfigureExports", - "stopTriggers" + "dataFactory::linkedService_remoteHubStorage" ] }, - "startTriggers": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]" + }, + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]" + }, + "appRegistration": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_ADF.StartTriggers", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.RemoteHub_Register", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -17606,34 +21857,14 @@ "app": { "value": "[parameters('app')]" }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" - }, - "scriptContent": { - "value": "[variables('$fxv#2')]" + "version": { + "value": "[variables('finOpsToolkitVersion')]" }, - "environmentVariables": { + "features": { "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('dataFactoryName')]" - }, - { - "name": "Triggers", - "value": "[join(variables('allHubTriggers'), '|')]" - }, - { - "name": "Pipelines", - "value": "[join(createArray(format('{0}_InitializeHub', variables('safeConfigContainerName'))), '|')]" - } + "DataFactory", + "KeyVault", + "Storage" ] } }, @@ -17644,22 +21875,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" + "version": "0.39.26.7824", + "templateHash": "5436870138046688593" } }, "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "_1.HubProperties": { "type": "object", "properties": { @@ -17778,6 +21998,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -17804,6 +22027,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -17834,38 +22058,38 @@ } } }, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -17874,22 +22098,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -17897,1339 +22120,2522 @@ } } }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." + "description": "Required. FinOps hub app getting deployed." } }, - "identityName": { + "version": { "type": "string", "metadata": { - "description": "Required. Name of the managed identity to create." + "description": "Required. Version number of the FinOps hub app." } }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." } }, - "scriptContent": { + "storageRoles": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + } + }, + "telemetryString": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0}', parameters('app').id)]", + "version": "[parameters('version')]" + } + }, + "resources": [] } }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", + "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + }, + "resources": { + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", + "properties": { + "name": "[parameters('app').storage]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "storageAccount" + ] + }, + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", + "properties": { + "name": "[parameters('app').keyVault]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "keyVault" + ] + }, + "dataFactory::managedVirtualNetwork": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "properties": {}, + "dependsOn": [ + "dataFactory" + ] + }, + "dataFactory::managedIntegrationRuntime": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "default", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('app').hub.location]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedVirtualNetwork" + ] }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" + "dataFactory::linkedService_keyVault": { + "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "keyVault" + ] }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", + "dataFactory::linkedService_storageAccount": { + "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "storageAccount" + ] + }, + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] }, "dependsOn": [ - "identity" + "blobEndpoint" ] }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } + ] }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", "dependsOn": [ - "identity", - "identityRoleAssignments" + "dfsEndpoint" ] - } - } - } - }, - "dependsOn": [ - "deleteOldResources", - "pipeline_InitializeHub", - "trigger_DailySchedule", - "trigger_ExportManifestAdded", - "trigger_IngestionManifestAdded", - "trigger_MonthlySchedule", - "trigger_SettingsUpdated", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" - ] - } - }, - "outputs": { - "resourceId": { - "type": "string", - "metadata": { - "description": "The Resource ID of the Data factory." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "The Name of the Azure Data Factory instance." - }, - "value": "[parameters('dataFactoryName')]" - } - } - } - }, - "dependsOn": [ - "cmExports", - "core", - "dataExplorer", - "remoteHub" - ] - }, - "remoteHub": { - "condition": "[not(empty(parameters('remoteHubStorageKey')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.RemoteHub", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[variables('hub')]" - }, - "remoteStorageKey": { - "value": "[parameters('remoteHubStorageKey')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "3708155483370559900" - } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] }, - "keyVault": { - "type": "string" + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] }, - "scripts": { - "type": "string" + "appTelemetry": { + "condition": "[parameters('app').hub.options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "properties": "[variables('telemetryProps')]" }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } + } }, - "keyVaultSku": { - "type": "string" + "storageRoleAssignments": { + "copy": { + "name": "storageRoleAssignments", + "count": "[length(variables('factoryStorageRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "storageAccount" + ] }, - "networkAddressPrefix": { - "type": "string" + "triggerManagerIdentity": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "dependsOn": [ + "dataFactory" + ] }, - "privateRouting": { - "type": "bool" + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "triggerManagerIdentity" + ] }, - "publisherIsolation": { - "type": "bool" + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]", + "location": "[parameters('app').hub.location]", + "sku": { + "name": "[parameters('app').hub.options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" }, - "storageInfrastructureEncryption": { - "type": "bool" + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance properties." - } - }, - "remoteStorageKey": { - "type": "securestring", - "metadata": { - "description": "Required. Create and store a key for a remote storage account." - } - } - }, - "variables": { - "$fxv#0": "12.0" - }, - "resources": { - "appRegistration": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.RemoteHub_Register", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[parameters('hub')]" - }, - "publisher": { - "value": "Microsoft FinOps hubs" - }, - "namespace": { - "value": "Microsoft.FinOpsHubs" - }, - "appName": { - "value": "RemoteHub" - }, - "displayName": { - "value": "FinOps hub remote relay" - }, - "appVersion": { - "value": "[variables('$fxv#0')]" - }, - "features": { - "value": [ - "KeyVault", - "Storage" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "15179190433979236138" - } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "blob" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { - "networkId": { - "type": "string" + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" }, - "networkName": { - "type": "string" + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('app').hub.options.keyVaultSku]", + "family": "A" }, - "scriptStorage": { - "type": "string" + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + } + }, + "dependsOn": [ + "dataFactory" + ] + }, + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('app').keyVault)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.keyVault]" }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "groupIds": [ + "vault" + ] } } + ] + }, + "dependsOn": [ + "keyVault" + ] + }, + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + } }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } }, - "storage": { - "type": "string" + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" } } } }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." + "dependsOn": [ + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", + "getStoragePrivateEndpointConnections", + "keyVault" + ] + }, + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } } - } + }, + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections", + "keyVault" + ] }, - "_1.IdNameObject": { - "type": "object", + "getStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetStoragePrivateEndpointConnections", "properties": { - "id": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "name": { - "type": "string" + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } } }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppFeature": { - "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], - "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dependsOn": [ + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", + "stopTriggers", + "storageAccount" + ] }, - "HubAppProperties": { - "type": "object", + "approveStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveStoragePrivateEndpointConnections", "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "tags": { - "type": "object" + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } }, - "tags": { - "type": "object" + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" } } - }, - "hub": { - "$ref": "#/definitions/HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" } }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dependsOn": [ + "getStoragePrivateEndpointConnections", + "storageAccount" + ] }, - "HubProperties": { - "type": "object", + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" + "expressionEvaluationOptions": { + "scope": "inner" }, - "version": { - "type": "string" + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('app').dataFactory]" + } + ] + } }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } }, - "storageInfrastructureEncryption": { - "type": "bool" + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - { - "type": "string", - "name": "resourceType" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - { - "type": "bool", - "nullable": true, - "name": "forceAppTags" + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } - ], - "output": { - "type": "object", - "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "getPublisherTags": { - "parameters": [ - { + "parameters": { + "app": { "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "newApp": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - { + "identityName": { "type": "string", - "name": "publisherDisplayName" + "metadata": { + "description": "Required. Name of the managed identity to create." + } }, - { + "scriptName": { "type": "string", - "name": "publisherName" + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } }, - { + "scriptContent": { "type": "string", - "name": "appPartialName" + "metadata": { + "description": "Required. Name of the deployment script to create." + } }, - { + "arguments": { "type": "string", - "name": "appDisplayName" + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } }, - { - "type": "string", - "name": "version" + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } } - ], - "output": { - "$ref": "#/definitions/HubAppProperties", - "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" }, - "metadata": { - "description": "Creates a new FinOps hub app configuration object.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } + }, + "dependsOn": [ + "appTelemetry", + "dataFactory", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" + ] + } + }, + "outputs": { + "dataFactoryId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Data Factory instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" + }, + "keyVaultId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Key Vault instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + }, + "triggerManagerIdentityName": { + "type": "string", + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + } + } + } + } + }, + "keyVault_secret": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "keyVault_secret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vaultName": { + "value": "[parameters('app').keyVault]" + }, + "secretName": { + "value": "[variables('storageKeySecretName')]" + }, + "secretValue": { + "value": "[parameters('remoteStorageKey')]" + }, + "secretExpirationInSeconds": { + "value": 1702648632 + }, + "secretNotBeforeInSeconds": { + "value": 10000 + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8808837526156050188" + } + }, + "parameters": { + "vaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Key Vault instance." + } + }, + "secretName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Key Vault secret to create or update." + } + }, + "secretValue": { + "type": "securestring", + "metadata": { + "description": "Required. Value of the Key Vault secret." + } + }, + "secretExpirationInSeconds": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "Optional. Value of the Key Vault secret expiration date (exp) property. This is represented as seconds since Jan 1, 1970." + } + }, + "secretNotBeforeInSeconds": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "Optional. Value of the Key Vault secret not before date (nbf) property. This is represented as seconds since Jan 1, 1970." + } + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('vaultName'), parameters('secretName'))]", + "properties": { + "attributes": "[union(createObject('enabled', true()), if(lessOrEquals(parameters('secretExpirationInSeconds'), 0), createObject(), createObject('exp', parameters('secretExpirationInSeconds'))), if(lessOrEquals(parameters('secretNotBeforeInSeconds'), 0), createObject(), createObject('nbf', parameters('secretNotBeforeInSeconds'))))]", + "value": "[parameters('secretValue')]" } + } + ], + "outputs": { + "secretName": { + "type": "string", + "metadata": { + "description": "Name of the Key Vault secret." + }, + "value": "[parameters('secretName')]" + } + } + } + } + } + }, + "outputs": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Name of the Key Vault instance." + }, + "value": "[parameters('app').keyVault]" + } + } + } + }, + "dependsOn": [ + "core" + ] + }, + "deleteOldResources": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.DeleteOldResources", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('core').outputs.app.value]" + }, + "identityName": { + "value": "[reference('core').outputs.triggerManagerIdentityName.value]" + }, + "scriptContent": { + "value": "[variables('$fxv#1')]" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[reference('core').outputs.app.value.dataFactory]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - { - "namespace": "_1", - "members": { - "newAppInternal": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherSuffix" - }, - { - "type": "object", - "name": "publisherTags" - }, - { - "type": "string", - "name": "appName" - }, - { - "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" - } - ], - "output": { - "$ref": "#/definitions/HubAppProperties", - "value": { - "name": "[parameters('appName')]", - "displayName": "[parameters('appDisplayName')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", - "publisher": { - "name": "[parameters('publisherName')]", - "displayName": "[parameters('publisherDisplayName')]", - "suffix": "[parameters('publisherSuffix')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" - }, - "hub": "[parameters('hub')]", - "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "safeStorageName": { - "parameters": [ - { - "type": "string", - "name": "name" - } - ], - "output": { - "type": "string", - "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } - ], - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance properties." - } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app publisher." - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } + } + }, + "dependsOn": [ + "core" + ] + }, + "startTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.StartTriggers", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('core').outputs.app.value]" + }, + "dataFactoryInstances": { + "value": [ + "[reference('core').outputs.app.value.dataFactory]", + "[reference('cmExports').outputs.app.value.dataFactory]" + ] + }, + "identityName": { + "value": "[reference('core').outputs.triggerManagerIdentityName.value]" + }, + "startAllTriggers": { + "value": true + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "4749940909471549408" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "namespace": { - "type": "string", - "metadata": { - "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } + "keyVaultSku": { + "type": "string" }, - "appName": { - "type": "string", - "metadata": { - "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } + "networkAddressPrefix": { + "type": "string" }, - "displayName": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app." - } + "privateRouting": { + "type": "bool" }, - "appVersion": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Version number of the FinOps hub app." - } + "publisherIsolation": { + "type": "bool" }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." - } + "storageInfrastructureEncryption": { + "type": "bool" }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } + "storageSku": { + "type": "string" } - }, - "variables": { - "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", - "version": "[parameters('appVersion')]" - } - }, - "resources": [] - } - }, - "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" - }, - "resources": { - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', variables('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] + "queue": { + "$ref": "#/definitions/_1.IdNameObject" }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] + "dataFactory": { + "type": "string" }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" - } - } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] + "keyVault": { + "type": "string" }, - "appTelemetry": { - "condition": "[parameters('hub').options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", - "properties": "[variables('telemetryProps')]" + "scripts": { + "type": "string" }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[variables('app').dataFactory]", - "location": "[variables('app').hub.location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + }, + "dataFactoryInstances": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of Azure Data Factory instances to start triggers for. Can be up to 1 per publisher." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to use when starting the triggers." + } + }, + "startAllTriggers": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Start all triggers for the Data Factory instances. Default: false." + } + }, + "startPipelines": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. List of pipelines to run. Default: [] (no pipelines)." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "uniqueInstances": "[union(filter(parameters('dataFactoryInstances'), lambda('adf', not(empty(lambdaVariables('adf'))))), createArray())]" + }, + "resources": { + "initialize": { + "copy": { + "name": "initialize", + "count": "[length(variables('uniqueInstances'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[if(lessOrEquals(length(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()])), 64), format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), substring(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), 0, 64))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[parameters('identityName')]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[variables('uniqueInstances')[copyIndex()]]" + }, + { + "name": "Pipelines", + "value": "[join(parameters('startPipelines'), '|')]" }, + { + "name": "StartAllTriggers", + "value": "[string(parameters('startAllTriggers'))]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" + "name": { + "type": "string" + }, + "value": { + "type": "string" } } }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[variables('app').storage]", - "location": "[parameters('hub').location]", - "sku": { - "name": "[parameters('hub').options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" - }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', variables('app').storage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "_1.HubProperties": { + "type": "object", "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" + "id": { + "type": "string" }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "blob" - ] + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" - }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', variables('app').storage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "dfs" - ] + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } - ] + } }, - "dependsOn": [ - "storageAccount" - ] + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[variables('app').keyVault]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", + "_1.HubRoutingProperties": { + "type": "object", "properties": { - "sku": { - "name": "[parameters('hub').options.keyVaultSku]", - "family": "A" + "networkId": { + "type": "string" }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } - }, - "dependsOn": [ - "dataFactory" - ] + } }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', variables('app').keyVault)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "HubAppProperties": { + "type": "object", "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.keyVault]" + "id": { + "type": "string" }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } }, - "dependsOn": [ - "keyVault" - ] + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } }, - "outputs": { + "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "FinOps hub app configuration." - }, - "value": "[variables('app')]" + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - } - } - } - } - }, - "keyVault_secret": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "keyVault_secret", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "vaultName": { - "value": "[reference('appRegistration').outputs.app.value.keyVault]" - }, - "secretName": { - "value": "[format('{0}-storage-key', toLower(reference('appRegistration').outputs.app.value.hub.name))]" - }, - "secretValue": { - "value": "[parameters('remoteStorageKey')]" - }, - "secretExpirationInSeconds": { - "value": 1702648632 - }, - "secretNotBeforeInSeconds": { - "value": 10000 - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "338893459125049689" - } - }, - "parameters": { - "vaultName": { + "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the publisher-specific Key Vault instance." + "description": "Required. Name of the managed identity to create." } }, - "secretName": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Required. Name of the Key Vault secret to create or update." + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." } }, - "secretValue": { - "type": "securestring", + "scriptContent": { + "type": "string", "metadata": { - "description": "Required. Value of the Key Vault secret." + "description": "Required. Name of the deployment script to create." } }, - "secretExpirationInSeconds": { - "type": "int", - "defaultValue": -1, + "arguments": { + "type": "string", + "defaultValue": "", "metadata": { - "description": "Optional. Value of the Key Vault secret expiration date (exp) property. This is represented as seconds since Jan 1, 1970." + "description": "Optional. Additional arguments to pass into the deployment script." } }, - "secretNotBeforeInSeconds": { - "type": "int", - "defaultValue": -1, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], "metadata": { - "description": "Optional. Value of the Key Vault secret not before date (nbf) property. This is represented as seconds since Jan 1, 1970." + "description": "Optional. Environment variables to use for the deployment script." } } }, - "resources": [ - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('vaultName'), parameters('secretName'))]", + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "attributes": "[union(createObject('enabled', true()), if(lessOrEquals(parameters('secretExpirationInSeconds'), 0), createObject(), createObject('exp', parameters('secretExpirationInSeconds'))), if(lessOrEquals(parameters('secretNotBeforeInSeconds'), 0), createObject(), createObject('nbf', parameters('secretNotBeforeInSeconds'))))]", - "value": "[parameters('secretValue')]" - } - } - ], - "outputs": { - "secretName": { - "type": "string", - "metadata": { - "description": "Name of the Key Vault secret." + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" }, - "value": "[parameters('secretName')]" + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } - }, - "dependsOn": [ - "appRegistration" - ] - } - }, - "outputs": { - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Name of the Key Vault instance." - }, - "value": "[reference('appRegistration').outputs.app.value.keyVault]" + } } } } - } + }, + "dependsOn": [ + "cmExports", + "core" + ] } }, "outputs": { @@ -19280,28 +24686,28 @@ "metadata": { "description": "The resource ID of the Data Explorer cluster." }, - "value": "[if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.clusterId.value)]" + "value": "[if(not(variables('useAzureDataExplorer')), '', reference('analytics').outputs.clusterId.value)]" }, "clusterUri": { "type": "string", "metadata": { "description": "The URI of the Data Explorer cluster." }, - "value": "[if(variables('useFabric'), parameters('fabricQueryUri'), if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.clusterUri.value))]" + "value": "[if(variables('useFabric'), parameters('fabricQueryUri'), if(not(variables('useAzureDataExplorer')), '', reference('analytics').outputs.clusterUri.value))]" }, "ingestionDbName": { "type": "string", "metadata": { "description": "The name of the Data Explorer database used for ingesting data." }, - "value": "[if(variables('useFabric'), 'Ingestion', if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.ingestionDbName.value))]" + "value": "[if(or(variables('useFabric'), variables('useAzureDataExplorer')), reference('analytics').outputs.ingestionDbName.value, '')]" }, "hubDbName": { "type": "string", "metadata": { "description": "The name of the Data Explorer database used for querying data." }, - "value": "[if(variables('useFabric'), 'Hub', if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.hubDbName.value))]" + "value": "[if(or(variables('useFabric'), variables('useAzureDataExplorer')), reference('analytics').outputs.hubDbName.value, '')]" }, "managedIdentityId": { "type": "string", @@ -19342,70 +24748,70 @@ "metadata": { "description": "Name of the Data Factory instance." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.dataFactoryName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.dataFactoryName.value]" }, "storageAccountId": { "type": "string", "metadata": { "description": "Resource ID of the deployed storage account." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageAccountId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageAccountId.value]" }, "storageAccountName": { "type": "string", "metadata": { "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageAccountName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageAccountName.value]" }, "storageUrlForPowerBI": { "type": "string", "metadata": { "description": "URL to use when connecting custom Power BI reports to your data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageUrlForPowerBI.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageUrlForPowerBI.value]" }, "clusterId": { "type": "string", "metadata": { "description": "Resource ID of the Data Explorer cluster." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.clusterId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.clusterId.value]" }, "clusterUri": { "type": "string", "metadata": { "description": "URI of the Data Explorer cluster." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.clusterUri.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.clusterUri.value]" }, "ingestionDbName": { "type": "string", "metadata": { "description": "Name of the Data Explorer database used for ingesting data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.ingestionDbName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.ingestionDbName.value]" }, "hubDbName": { "type": "string", "metadata": { "description": "Name of the Data Explorer database used for querying data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.hubDbName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.hubDbName.value]" }, "managedIdentityId": { "type": "string", "metadata": { "description": "Object ID of the Data Factory managed identity. This will be needed when configuring managed exports." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.managedIdentityId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.managedIdentityId.value]" }, "managedIdentityTenantId": { "type": "string", "metadata": { "description": "Azure AD tenant ID. This will be needed when configuring managed exports." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.managedIdentityTenantId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.managedIdentityTenantId.value]" } } } \ No newline at end of file diff --git a/docs/deploy/finops-hub-12.0.ui.json b/docs/deploy/finops-hub-12.0.ui.json index 16b32e2ef..201aefd47 100644 --- a/docs/deploy/finops-hub-12.0.ui.json +++ b/docs/deploy/finops-hub-12.0.ui.json @@ -696,6 +696,51 @@ } ], "visible": true + }, + { + "name": "remoteHub", + "type": "Microsoft.Common.Section", + "label": "Remote hub configuration", + "elements": [ + { + "name": "remoteHubIntro", + "type": "Microsoft.Common.TextBlock", + "visible": true, + "options": { + "text": "Configure this hub to send data to a remote FinOps hub in another tenant or subscription. This enables cross-tenant cost management scenarios where a central tenant collects cost data from multiple tenants. Leave these fields empty if this is not a remote hub setup." + } + }, + { + "name": "remoteHubStorageUri", + "type": "Microsoft.Common.TextBox", + "label": "Remote hub storage URI", + "toolTip": "Data Lake storage endpoint from the remote hub storage account. Copy from the storage account Settings > Endpoints > Data Lake storage. Example: https://myremotehub.dfs.core.windows.net/", + "constraints": { + "required": false, + "regex": "^$|^https://.*\\.dfs\\.core\\.windows\\.net/?$", + "validationMessage": "Must be a valid Data Lake storage endpoint URL in the format: https://storageaccount.dfs.core.windows.net/" + }, + "visible": true + }, + { + "name": "remoteHubStorageKey", + "type": "Microsoft.Common.PasswordBox", + "label": { + "password": "Remote hub storage key" + }, + "toolTip": "Storage account access key for the remote hub. Copy from the remote hub storage account Security + networking > Access keys > key1/2 > Key.", + "constraints": { + "required": false, + "regex": "^$|^[A-Za-z0-9+/]{86}==$", + "validationMessage": "Must be a valid storage account access key (base64 encoded, ending with ==)" + }, + "options": { + "hideConfirmation": true + }, + "visible": true + } + ], + "visible": true } ] }, @@ -739,6 +784,8 @@ "ingestionRetentionInMonths": "[steps('retention').storage.ingestionMonths]", "dataExplorerRawRetentionInDays": "[steps('retention').dataExplorer.rawDays]", "dataExplorerFinalRetentionInMonths": "[steps('retention').dataExplorer.finalMonths]", + "remoteHubStorageUri": "[steps('advanced').remoteHub.remoteHubStorageUri]", + "remoteHubStorageKey": "[steps('advanced').remoteHub.remoteHubStorageKey]", "tagsByResource": "[steps('tags').tagsByResource]" } } diff --git a/docs/deploy/finops-hub-latest.json b/docs/deploy/finops-hub-latest.json index 4dd0a7f94..3a65ba896 100644 --- a/docs/deploy/finops-hub-latest.json +++ b/docs/deploy/finops-hub-latest.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "1039455044893847676" + "version": "0.39.26.7824", + "templateHash": "8677558940311187779" } }, "parameters": { @@ -233,7 +233,7 @@ "resources": [ { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "hub", "properties": { "expressionEvaluationOptions": { @@ -312,43 +312,29 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "7621563314037220493" + "version": "0.39.26.7824", + "templateHash": "13528846700335310209" } }, "definitions": { "_1.HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -357,25 +343,24 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -461,7 +446,7 @@ "apps": {}, "description": "FinOps hub instance properties.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -497,6 +482,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -523,6 +511,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -530,7 +519,7 @@ }, "description": "FinOps hub private network routing properties.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -549,7 +538,7 @@ "name": "Resource name.", "description": "Resource ID and name.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } } @@ -571,7 +560,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -595,7 +584,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -607,54 +596,38 @@ }, { "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherSuffix" - }, - { - "type": "object", - "name": "publisherTags" + "name": "id" }, { "type": "string", - "name": "appName" + "name": "name" }, { "type": "string", - "name": "appDisplayName" + "name": "publisher" }, { "type": "string", - "name": "version" + "name": "suffix" } ], "output": { "$ref": "#/definitions/_1.HubAppProperties", "value": { - "name": "[parameters('appName')]", - "displayName": "[parameters('appDisplayName')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", - "publisher": { - "name": "[parameters('publisherName')]", - "displayName": "[parameters('publisherDisplayName')]", - "suffix": "[parameters('publisherSuffix')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" - }, + "id": "[parameters('id')]", + "name": "[parameters('name')]", + "publisher": "[parameters('publisher')]", + "suffix": "[parameters('suffix')]", + "tags": "[union(parameters('hub').tags, createObject('ftk-hubapp-publisher', parameters('publisher')))]", "hub": "[parameters('hub')]", - "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" + "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('suffix'))), 1)), parameters('suffix')), '--', '-')]", + "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('suffix'))), 1)), parameters('suffix')), '--', '-')]", + "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('suffix')))), parameters('suffix'))]" } }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -742,6 +715,7 @@ "table": "[if(parameters('enablePublicAccess'), createObject('id', '', 'name', ''), _1.dnsZoneIdName('table'))]" }, "subnets": { + "dataExplorer": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'dataExplorer-subnet'))]", "dataFactory": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'private-endpoint-subnet'))]", "keyVault": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'private-endpoint-subnet'))]", "scripts": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'script-subnet'))]", @@ -755,7 +729,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -772,7 +746,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } } @@ -799,7 +773,7 @@ "metadata": { "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -811,33 +785,21 @@ }, { "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "appPartialName" + "name": "publisher" }, { "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" + "name": "app" } ], "output": { "$ref": "#/definitions/_1.HubAppProperties", - "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" + "value": "[_1.newAppInternal(parameters('hub'), format('{0}.{1}', parameters('publisher'), parameters('app')), parameters('app'), parameters('publisher'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisher'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisher'))))]" }, "metadata": { "description": "Creates a new FinOps hub app configuration object.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } }, @@ -891,7 +853,7 @@ "metadata": { "description": "Creates a new FinOps hub configuration object.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "fx/hub-types.bicep" } } } @@ -1140,12 +1102,12 @@ }, "variables": { "$fxv#0": "12.0", - "$fxv#1": "12.0", + "$fxv#1": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\n#\n$adfParams = @{\n ResourceGroupName = $env:DataFactoryResourceGroup\n DataFactoryName = $env:DataFactoryName\n}\n\n# Delete old triggers\n$triggers = Get-AzDataFactoryV2Trigger @adfParams -ErrorAction SilentlyContinue `\n| Where-Object { $_.Name -match '^msexports(_(setup|daily|monthly|extract|FileAdded))?$' }\n$DeploymentScriptOutputs[\"stopTriggers\"] = $triggers | Stop-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\n$DeploymentScriptOutputs[\"deleteTriggers\"] = $triggers | Remove-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\n\n# Delete old pipelines\n$DeploymentScriptOutputs[\"pipelines\"] = Get-AzDataFactoryV2Pipeline @adfParams -ErrorAction SilentlyContinue `\n| Where-Object { $_.Name -match '^(msexports_(backfill|extract|fill|get|run|setup|transform)|config_(BackfillData|ExportData|RunBackfill|RunExports))$' } `\n| Remove-AzDataFactoryV2Pipeline -Force -ErrorAction SilentlyContinue\n", "hub": "[__bicep.newHub(parameters('hubName'), parameters('location'), parameters('tags'), parameters('tagsByResource'), parameters('storageSku'), parameters('keyVaultSku'), parameters('enableInfrastructureEncryption'), parameters('enablePublicAccess'), parameters('virtualNetworkAddressPrefix'), parameters('enableDefaultTelemetry'))]", "useFabric": "[not(empty(parameters('fabricQueryUri')))]", - "deployDataExplorer": "[and(not(variables('useFabric')), not(empty(parameters('dataExplorerName'))))]", + "useAzureDataExplorer": "[and(not(variables('useFabric')), not(empty(parameters('dataExplorerName'))))]", "telemetryId": "00f120b5-2007-6120-0000-40b000000000", - "telemetryString": "[join(createArray(if(or(empty(parameters('remoteHubStorageUri')), empty(parameters('remoteHubStorageKey'))), '', 'R'), substring(split(parameters('storageSku'), '_')[1], 0, 1), if(not(variables('useFabric')), '', format('F{0}', parameters('fabricCapacityUnits'))), if(not(variables('deployDataExplorer')), '', format('X{0}', substring(parameters('dataExplorerSku'), 0, 1))), if(not(variables('deployDataExplorer')), '', replace(replace(replace(replace(replace(replace(replace(replace(split(split(parameters('dataExplorerSku'), 'Standard_')[1], '_')[0], 'C', ''), 'D', ''), 'E', ''), 'L', ''), 'a', ''), 'd', ''), 'i', ''), 's', '')), if(or(not(variables('deployDataExplorer')), equals(parameters('dataExplorerCapacity'), 1)), '', format('x{0}', parameters('dataExplorerCapacity'))), if(parameters('enablePublicAccess'), '', 'P')), '')]", + "telemetryString": "[join(createArray(if(or(empty(parameters('remoteHubStorageUri')), empty(parameters('remoteHubStorageKey'))), '', 'R'), substring(split(parameters('storageSku'), '_')[1], 0, 1), if(not(variables('useFabric')), '', format('F{0}', parameters('fabricCapacityUnits'))), if(not(variables('useAzureDataExplorer')), '', format('X{0}', substring(parameters('dataExplorerSku'), 0, 1))), if(not(variables('useAzureDataExplorer')), '', replace(replace(replace(replace(replace(replace(replace(replace(split(split(parameters('dataExplorerSku'), 'Standard_')[1], '_')[0], 'C', ''), 'D', ''), 'E', ''), 'L', ''), 'a', ''), 'd', ''), 'i', ''), 's', '')), if(or(not(variables('useAzureDataExplorer')), equals(parameters('dataExplorerCapacity'), 1)), '', format('x{0}', parameters('dataExplorerCapacity'))), if(parameters('enablePublicAccess'), '', 'P')), '')]", "_1.finOpsToolkitVersion": "12.0" }, "resources": { @@ -1170,18 +1132,33 @@ } } }, - "infrastructure": { + "core": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Infrastructure", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "hub": { - "value": "[variables('hub')]" + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'Core')]" + }, + "scopesToMonitor": { + "value": "[parameters('scopesToMonitor')]" + }, + "msexportRetentionInDays": { + "value": "[parameters('exportRetentionInDays')]" + }, + "ingestionRetentionInMonths": { + "value": "[parameters('ingestionRetentionInMonths')]" + }, + "rawRetentionInDays": { + "value": "[parameters('dataExplorerRawRetentionInDays')]" + }, + "finalRetentionInMonths": { + "value": "[parameters('dataExplorerFinalRetentionInMonths')]" } }, "template": { @@ -1191,11 +1168,97 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "8095489755767003461" + "version": "0.39.26.7824", + "templateHash": "13614615614696914627" } }, "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, "_1.HubRoutingProperties": { "type": "object", "properties": { @@ -1228,6 +1291,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -1254,6 +1320,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -1261,7 +1328,7 @@ }, "description": "FinOps hub private network routing properties.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "../../fx/hub-types.bicep" } } }, @@ -1280,11 +1347,11 @@ "name": "Resource name.", "description": "Resource ID and name.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "../../fx/hub-types.bicep" } } }, - "HubProperties": { + "HubAppProperties": { "type": "object", "properties": { "id": { @@ -1293,1082 +1360,400 @@ "name": { "type": "string" }, - "location": { + "publisher": { "type": "string" }, - "tags": { - "type": "object" + "suffix": { + "type": "string" }, - "tagsByResource": { + "tags": { "type": "object" }, - "version": { + "dataFactory": { "type": "string" }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } + "keyVault": { + "type": "string" }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" + "storage": { + "type": "string" }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "../../fx/hub-types.bicep" } } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getHubTags": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(parameters('hub').tags, coalesce(tryGet(parameters('hub').tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", + "app": { + "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub instance properties." + "description": "Required. FinOps hub app getting deployed." } - } - }, - "variables": { - "nsgName": "[format('{0}-nsg', parameters('hub').routing.networkName)]", - "finopsHubSubnetName": "private-endpoint-subnet", - "scriptSubnetName": "script-subnet", - "dataExplorerSubnetName": "dataExplorer-subnet", - "subnets": "[if(not(parameters('hub').options.privateRouting), createArray(), createArray(createObject('name', variables('finopsHubSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 0), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('scriptSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'delegations', createArray(createObject('name', 'Microsoft.ContainerInstance/containerGroups', 'properties', createObject('serviceName', 'Microsoft.ContainerInstance/containerGroups'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('dataExplorerSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 27, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName')))))))]" - }, - "resources": { - "vNet::finopsHubSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('finopsHubSubnetName'))]", - "dependsOn": [ - "vNet" - ] }, - "vNet::scriptSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('scriptSubnetName'))]", - "dependsOn": [ - "vNet" - ] + "scopesToMonitor": { + "type": "array", + "metadata": { + "description": "Optional. List of scope IDs to monitor and ingest cost for." + } }, - "vNet::dataExplorerSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('dataExplorerSubnetName'))]", - "dependsOn": [ - "vNet" - ] + "msexportRetentionInDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Number of days of data to retain in the msexports container. Default: 0." + } }, - "blobPrivateDnsZone::blobPrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.blob.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.blob.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "blobPrivateDnsZone" - ] + "ingestionRetentionInMonths": { + "type": "int", + "defaultValue": 13, + "metadata": { + "description": "Optional. Number of months of data to retain in the ingestion container. Default: 13." + } }, - "dfsPrivateDnsZone::dfsPrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.dfs.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.dfs.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "dfsPrivateDnsZone" - ] + "rawRetentionInDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." + } }, - "queuePrivateDnsZone::queuePrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.queue.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.queue.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "finalRetentionInMonths": { + "type": "int", + "defaultValue": 13, + "metadata": { + "description": "Optional. Number of months of data to retain in the Data Explorer *_final_v* tables. Default: 13." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nWrite-Output \"Updating settings.json file...\"\nWrite-Output \" Storage account: $env:storageAccountName\"\nWrite-Output \" Container: $env:containerName\"\n\n$validateScopes = { $_.Length -gt 45 }\n\n# Initialize variables\n$fileName = 'settings.json'\n$filePath = Join-Path -Path . -ChildPath $fileName\n$newScopes = $env:scopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Download existing settings, if they exist\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\nif ($blob)\n{\n $text = Get-Content $filePath -Raw\n Write-Output \"---------\"\n Write-Output $text\n Write-Output \"---------\"\n $json = $text | ConvertFrom-Json\n Write-Output \"Existing settings.json file found. Updating...\"\n\n # Rename exportScopes to scopes + convert to object array\n if ($json.exportScopes)\n {\n Write-Output \" Updating exportScopes...\"\n if ($json.exportScopes[0] -is [string])\n {\n Write-Output \" Converting string array to object array...\"\n $json.exportScopes = @($json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } })\n if (-not ($json.exportScopes -is [array]))\n {\n Write-Output \" Converting single object to object array...\"\n $json.exportScopes = @($json.exportScopes)\n }\n }\n\n Write-Output \" Renaming to 'scopes'...\"\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\n $json.PSObject.Properties.Remove('exportScopes')\n }\n\n # Force string array to object array with unique values\n if ($json.scopes)\n {\n Write-Output \" Converting string array to object array...\"\n $scopeArray = @()\n $json.scopes | Where-Object $validateScopes | ForEach-Object { $scopeArray += $_ } | Select-Object -Unique\n $json.scopes = @() + $scopeArray\n }\n}\n\n# Set default if not found\nif (!$json)\n{\n Write-Output \"No existing settings.json file found. Creating new file...\"\n $json = [ordered]@{\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\n type = 'HubInstance'\n version = ''\n learnMore = 'https://aka.ms/finops/hubs'\n scopes = @()\n retention = @{\n 'msexports' = @{\n days = 0\n }\n 'ingestion' = @{\n months = 13\n }\n 'raw' = @{\n days = 0\n }\n 'final' = @{\n months = 13\n }\n }\n }\n\n $text = $json | ConvertTo-Json\n Write-Output \"---------\"\n Write-Output $text\n Write-Output \"---------\"\n}\n\n# Set default retention\nif (!($json.retention))\n{\n # In case the retention object is not present in the settings.json file (versions before 0.4), add it with default values\n $retention = @\"\n {\n \"msexports\": {\n \"days\": 0\n },\n \"ingestion\": {\n \"months\": 13\n },\n \"raw\": {\n \"days\": 0\n },\n \"final\": {\n \"months\": 13\n }\n }\n\"@\n $json | Add-Member -Name retention -Value (ConvertFrom-Json $retention) -MemberType NoteProperty\n}\n\n# Set or update msexports retention\nif (!($json.retention.msexports))\n{\n $json.retention | Add-Member -Name msexports -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:msexportRetentionInDays)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.msexports.days = [Int32]::Parse($env:msexportRetentionInDays)\n}\n\n# Set or update ingestion retention\nif (!($json.retention.ingestion))\n{\n $json.retention | Add-Member -Name ingestion -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:ingestionRetentionInMonths)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.ingestion.months = [Int32]::Parse($env:ingestionRetentionInMonths)\n}\n\n# Set or update raw retention\nif (!($json.retention.raw))\n{\n $json.retention | Add-Member -Name raw -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:rawRetentionInDays)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.raw.days = [Int32]::Parse($env:rawRetentionInDays)\n}\n\n# Set or update final retention\nif (!($json.retention.final))\n{\n $json.retention | Add-Member -Name final -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:finalRetentionInMonths)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.final.months = [Int32]::Parse($env:finalRetentionInMonths)\n}\n\n# Updating settings\nWrite-Output \"Updating version to $env:ftkVersion...\"\n$json.version = $env:ftkVersion\n$json.scopes = (@() + $json.scopes + $newScopes) | Select-Object -Unique\nif ($null -eq $json.scopes) { $json.scopes = @() }\n$text = $json | ConvertTo-Json\nWrite-Output \"---------\"\nWrite-Output $text\nWrite-Output \"---------\"\n$text | Out-File $filePath\n\n# Upload new/updated settings\nWrite-Output \"Uploading settings.json file...\"\nSet-AzStorageBlobContent @storageContext -File $filePath -Force | Out-Null\n", + "CONFIG": "config", + "INGESTION": "ingestion", + "finOpsToolkitVersion": "12.0" + }, + "resources": { + "dataFactory::dataset_config": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]", "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" + "linkedServiceName": { + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + }, + "type": "Json", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().fileName}", + "type": "Expression" + }, + "folderPath": { + "value": "@{dataset().folderPath}", + "type": "Expression" + } + } + }, + "parameters": { + "fileName": { + "type": "String", + "defaultValue": "settings.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[reference('configContainer').outputs.containerName.value]" + } } }, "dependsOn": [ - "queuePrivateDnsZone" + "appRegistration", + "configContainer" ] }, - "tablePrivateDnsZone::tablePrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.table.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.table.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "dataFactory::dataset_ingestion": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('INGESTION'))]", "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" + }, + "fileSystem": "[reference('ingestionContainer').outputs.containerName.value]" + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" } }, "dependsOn": [ - "tablePrivateDnsZone" + "appRegistration", + "ingestionContainer" ] }, - "scriptEndpoint::scriptPrivateDnsZoneGroup": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', string(parameters('hub').routing.scriptStorage)), 'blob-endpoint-zone')]", + "dataFactory::dataset_ingestion_files": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', variables('INGESTION')))]", "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', string(parameters('hub').routing.dnsZones.blob.name))]" - } + "annotations": [], + "parameters": { + "folderPath": { + "type": "String" } - ] - }, - "dependsOn": [ - "blobPrivateDnsZone", - "scriptEndpoint" - ] - }, - "nsg": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2023-11-01", - "name": "[variables('nsgName')]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/networkSecurityGroups')]", - "properties": { - "securityRules": [ - { - "name": "AllowVnetInBound", - "properties": { - "priority": 100, - "direction": "Inbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "VirtualNetwork", - "destinationAddressPrefix": "VirtualNetwork" - } - }, - { - "name": "AllowAzureLoadBalancerInBound", - "properties": { - "priority": 200, - "direction": "Inbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "AzureLoadBalancer", - "destinationAddressPrefix": "*" - } - }, - { - "name": "DenyAllInBound", - "properties": { - "priority": 4096, - "direction": "Inbound", - "access": "Deny", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*" - } - }, - { - "name": "AllowVnetOutBound", - "properties": { - "priority": 100, - "direction": "Outbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "VirtualNetwork", - "destinationAddressPrefix": "VirtualNetwork" - } - }, - { - "name": "AllowInternetOutBound", - "properties": { - "priority": 200, - "direction": "Outbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "Internet" - } - }, - { - "name": "DenyAllOutBound", - "properties": { - "priority": 4096, - "direction": "Outbound", - "access": "Deny", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*" + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileSystem": "[reference('ingestionContainer').outputs.containerName.value]", + "folderPath": { + "value": "@dataset().folderPath", + "type": "Expression" } } - ] - } - }, - "vNet": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2023-11-01", - "name": "[parameters('hub').routing.networkName]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/virtualNetworks')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[parameters('hub').options.networkAddressPrefix]" - ] }, - "subnets": "[variables('subnets')]" + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + } }, "dependsOn": [ - "nsg" - ] - }, - "blobPrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.dfs.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "queuePrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.queue.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "tablePrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.table.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" + "appRegistration", + "ingestionContainer" ] }, - "scriptStorageAccount": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[string(parameters('hub').routing.scriptStorage)]", - "location": "[parameters('hub').location]", - "sku": { - "name": "Standard_LRS" - }, - "kind": "StorageV2", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/storageAccounts')]", - "properties": { - "supportsHttpsTrafficOnly": true, - "allowSharedKeyAccess": true, - "isHnsEnabled": false, - "minimumTlsVersion": "TLS1_2", - "allowBlobPublicAccess": false, - "publicNetworkAccess": "Enabled", - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Deny", - "virtualNetworkRules": [ - { - "id": "[parameters('hub').routing.subnets.scripts]", - "action": "Allow" - } - ] - } - }, + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", "dependsOn": [ - "vNet::scriptSubnet" + "appRegistration" ] }, - "scriptEndpoint": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', string(parameters('hub').routing.scriptStorage))]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateEndpoints')]", + "infrastructure": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_Infrastructure", "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" + "expressionEvaluationOptions": { + "scope": "inner" }, - "privateLinkServiceConnections": [ - { - "name": "scriptLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', string(parameters('hub').routing.scriptStorage))]", - "groupIds": [ - "blob" - ] - } + "mode": "Incremental", + "parameters": { + "hub": { + "value": "[parameters('app').hub]" } - ] - }, - "dependsOn": [ - "scriptStorageAccount", - "vNet::scriptSubnet" - ] - } - }, - "outputs": { - "config": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "FinOps hub configuration settings." - }, - "value": "[parameters('hub')]" - }, - "vNetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the virtual network." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks', parameters('hub').routing.networkName))]" - }, - "vNetAddressSpace": { - "type": "array", - "metadata": { - "description": "Virtual network address prefixes." - }, - "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').addressSpace.addressPrefixes)]" - }, - "vNetSubnets": { - "type": "array", - "metadata": { - "description": "Virtual network subnets." - }, - "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').subnets)]" - }, - "finopsHubSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the FinOps hub network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('finopsHubSubnetName')))]" - }, - "scriptSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the script storage account network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('scriptSubnetName')))]" - }, - "dataExplorerSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Explorer network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('dataExplorerSubnetName')))]" - } - } - } - } - }, - "core": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[variables('hub')]" - }, - "telemetryString": { - "value": "[variables('telemetryString')]" - }, - "scopesToMonitor": { - "value": "[parameters('scopesToMonitor')]" - }, - "msexportRetentionInDays": { - "value": "[parameters('exportRetentionInDays')]" - }, - "ingestionRetentionInMonths": { - "value": "[parameters('ingestionRetentionInMonths')]" - }, - "rawRetentionInDays": { - "value": "[parameters('dataExplorerRawRetentionInDays')]" - }, - "finalRetentionInMonths": { - "value": "[parameters('dataExplorerFinalRetentionInMonths')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "15396896523851766061" - } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "5983517136492624194" } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" + }, + "definitions": { + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } }, - "scripts": { - "type": "string" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance to deploy the app to." - } - }, - "scopesToMonitor": { - "type": "array", - "metadata": { - "description": "Optional. List of scope IDs to monitor and ingest cost for." - } - }, - "msexportRetentionInDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Optional. Number of days of data to retain in the msexports container. Default: 0." - } - }, - "ingestionRetentionInMonths": { - "type": "int", - "defaultValue": 13, - "metadata": { - "description": "Optional. Number of months of data to retain in the ingestion container. Default: 13." - } - }, - "rawRetentionInDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." - } - }, - "finalRetentionInMonths": { - "type": "int", - "defaultValue": 13, - "metadata": { - "description": "Optional. Number of months of data to retain in the Data Explorer *_final_v* tables. Default: 13." - } - }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - } - }, - "variables": { - "$fxv#0": "12.0", - "$fxv#1": "12.0", - "$fxv#2": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nWrite-Output \"Updating settings.json file...\"\r\nWrite-Output \" Storage account: $env:storageAccountName\"\r\nWrite-Output \" Container: $env:containerName\"\r\n\r\n$validateScopes = { $_.Length -gt 45 }\r\n\r\n# Initialize variables\r\n$fileName = 'settings.json'\r\n$filePath = Join-Path -Path . -ChildPath $fileName\r\n$newScopes = $env:scopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Download existing settings, if they exist\r\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\r\nif ($blob)\r\n{\r\n $text = Get-Content $filePath -Raw\r\n Write-Output \"---------\"\r\n Write-Output $text\r\n Write-Output \"---------\"\r\n $json = $text | ConvertFrom-Json\r\n Write-Output \"Existing settings.json file found. Updating...\"\r\n\r\n # Rename exportScopes to scopes + convert to object array\r\n if ($json.exportScopes)\r\n {\r\n Write-Output \" Updating exportScopes...\"\r\n if ($json.exportScopes[0] -is [string])\r\n {\r\n Write-Output \" Converting string array to object array...\"\r\n $json.exportScopes = @($json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } })\r\n if (-not ($json.exportScopes -is [array]))\r\n {\r\n Write-Output \" Converting single object to object array...\"\r\n $json.exportScopes = @($json.exportScopes)\r\n }\r\n }\r\n\r\n Write-Output \" Renaming to 'scopes'...\"\r\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\r\n $json.PSObject.Properties.Remove('exportScopes')\r\n }\r\n\r\n # Force string array to object array with unique values\r\n if ($json.scopes)\r\n {\r\n Write-Output \" Converting string array to object array...\"\r\n $scopeArray = @()\r\n $json.scopes | Where-Object $validateScopes | ForEach-Object { $scopeArray += $_ } | Select-Object -Unique\r\n $json.scopes = @() + $scopeArray\r\n }\r\n}\r\n\r\n# Set default if not found\r\nif (!$json)\r\n{\r\n Write-Output \"No existing settings.json file found. Creating new file...\"\r\n $json = [ordered]@{\r\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\r\n type = 'HubInstance'\r\n version = ''\r\n learnMore = 'https://aka.ms/finops/hubs'\r\n scopes = @()\r\n retention = @{\r\n 'msexports' = @{\r\n days = 0\r\n }\r\n 'ingestion' = @{\r\n months = 13\r\n }\r\n 'raw' = @{\r\n days = 0\r\n }\r\n 'final' = @{\r\n months = 13\r\n }\r\n }\r\n }\r\n\r\n $text = $json | ConvertTo-Json\r\n Write-Output \"---------\"\r\n Write-Output $text\r\n Write-Output \"---------\"\r\n}\r\n\r\n# Set default retention\r\nif (!($json.retention))\r\n{\r\n # In case the retention object is not present in the settings.json file (versions before 0.4), add it with default values\r\n $retention = @\"\r\n {\r\n \"msexports\": {\r\n \"days\": 0\r\n },\r\n \"ingestion\": {\r\n \"months\": 13\r\n },\r\n \"raw\": {\r\n \"days\": 0\r\n },\r\n \"final\": {\r\n \"months\": 13\r\n }\r\n }\r\n\"@\r\n $json | Add-Member -Name retention -Value (ConvertFrom-Json $retention) -MemberType NoteProperty\r\n}\r\n\r\n# Set or update msexports retention\r\nif (!($json.retention.msexports))\r\n{\r\n $json.retention | Add-Member -Name msexports -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:msexportRetentionInDays)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.msexports.days = [Int32]::Parse($env:msexportRetentionInDays)\r\n}\r\n\r\n# Set or update ingestion retention\r\nif (!($json.retention.ingestion))\r\n{\r\n $json.retention | Add-Member -Name ingestion -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:ingestionRetentionInMonths)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.ingestion.months = [Int32]::Parse($env:ingestionRetentionInMonths)\r\n}\r\n\r\n# Set or update raw retention\r\nif (!($json.retention.raw))\r\n{\r\n $json.retention | Add-Member -Name raw -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:rawRetentionInDays)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.raw.days = [Int32]::Parse($env:rawRetentionInDays)\r\n}\r\n\r\n# Set or update final retention\r\nif (!($json.retention.final))\r\n{\r\n $json.retention | Add-Member -Name final -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:finalRetentionInMonths)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.final.months = [Int32]::Parse($env:finalRetentionInMonths)\r\n}\r\n\r\n# Updating settings\r\nWrite-Output \"Updating version to $env:ftkVersion...\"\r\n$json.version = $env:ftkVersion\r\n$json.scopes = (@() + $json.scopes + $newScopes) | Select-Object -Unique\r\nif ($null -eq $json.scopes) { $json.scopes = @() }\r\n$text = $json | ConvertTo-Json\r\nWrite-Output \"---------\"\r\nWrite-Output $text\r\nWrite-Output \"---------\"\r\n$text | Out-File $filePath\r\n\r\n# Upload new/updated settings\r\nWrite-Output \"Uploading settings.json file...\"\r\nSet-AzStorageBlobContent @storageContext -File $filePath -Force | Out-Null\r\n" - }, - "resources": { - "appRegistration": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_Register", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[parameters('hub')]" - }, - "publisher": { - "value": "Microsoft FinOps hubs" - }, - "namespace": { - "value": "Microsoft.FinOpsHubs" - }, - "appName": { - "value": "Core" - }, - "displayName": { - "value": "FinOps hub core" - }, - "appVersion": { - "value": "[variables('$fxv#0')]" - }, - "features": { - "value": [ - "DataFactory", - "Storage" - ] - }, - "telemetryString": { - "value": "[parameters('telemetryString')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "15179190433979236138" - } - }, - "definitions": { - "_1.HubRoutingProperties": { + "HubProperties": { "type": "object", "properties": { - "networkId": { + "id": { "type": "string" }, - "networkName": { + "name": { "type": "string" }, - "scriptStorage": { + "location": { "type": "string" }, - "dnsZones": { + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { "type": "object", "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "enableTelemetry": { + "type": "bool" }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "keyVaultSku": { + "type": "string" }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "networkAddressPrefix": { + "type": "string" }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } }, - "subnets": { + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { "type": "object", "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { + "suffix": { "type": "string" } } } }, "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppFeature": { - "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], - "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." }, "routing": "FinOps hub private network routing properties, if enabled.", "core": { @@ -2377,7 +1762,7 @@ "apps": {}, "description": "FinOps hub instance properties.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "../../fx/hub-types.bicep" } } } @@ -2386,56 +1771,7 @@ { "namespace": "__bicep", "members": { - "getAppTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - }, - { - "type": "bool", - "nullable": true, - "name": "forceAppTags" - } - ], - "output": { - "type": "object", - "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "getPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "newApp": { + "getHubTags": { "parameters": [ { "$ref": "#/definitions/HubProperties", @@ -2443,511 +1779,434 @@ }, { "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "appPartialName" - }, - { - "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" + "name": "resourceType" } ], "output": { - "$ref": "#/definitions/HubAppProperties", - "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" + "type": "object", + "value": "[union(parameters('hub').tags, coalesce(tryGet(parameters('hub').tagsByResource, parameters('resourceType')), createObject()))]" }, "metadata": { - "description": "Creates a new FinOps hub app configuration object.", + "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "sourceTemplate": "../../fx/hub-types.bicep" } } } } + } + ], + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "Required. FinOps hub instance properties." + } + } + }, + "variables": { + "nsgName": "[format('{0}-nsg', parameters('hub').routing.networkName)]", + "finopsHubSubnetName": "private-endpoint-subnet", + "scriptSubnetName": "script-subnet", + "dataExplorerSubnetName": "dataExplorer-subnet", + "subnets": "[if(not(parameters('hub').options.privateRouting), createArray(), createArray(createObject('name', variables('finopsHubSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 0), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('scriptSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'delegations', createArray(createObject('name', 'Microsoft.ContainerInstance/containerGroups', 'properties', createObject('serviceName', 'Microsoft.ContainerInstance/containerGroups'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('dataExplorerSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 27, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName')))))))]" + }, + "resources": { + "vNet::finopsHubSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('finopsHubSubnetName'))]", + "dependsOn": [ + "vNet" + ] }, - { - "namespace": "_1", - "members": { - "newAppInternal": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherSuffix" - }, - { - "type": "object", - "name": "publisherTags" - }, - { - "type": "string", - "name": "appName" - }, - { - "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" - } - ], - "output": { - "$ref": "#/definitions/HubAppProperties", - "value": { - "name": "[parameters('appName')]", - "displayName": "[parameters('appDisplayName')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", - "publisher": { - "name": "[parameters('publisherName')]", - "displayName": "[parameters('publisherDisplayName')]", - "suffix": "[parameters('publisherSuffix')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" - }, - "hub": "[parameters('hub')]", - "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "safeStorageName": { - "parameters": [ - { - "type": "string", - "name": "name" - } - ], - "output": { - "type": "string", - "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance properties." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app publisher." - } - }, - "namespace": { - "type": "string", - "metadata": { - "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - }, - "appName": { - "type": "string", - "metadata": { - "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - }, - "displayName": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app." - } - }, - "appVersion": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Version number of the FinOps hub app." - } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." - } - }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - } - }, - "variables": { - "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", - "version": "[parameters('appVersion')]" - } - }, - "resources": [] - } - }, - "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" - }, - "resources": { - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', variables('app').storage, 'default')]", + "vNet::scriptSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('scriptSubnetName'))]", "dependsOn": [ - "storageAccount" + "vNet" ] }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "vNet::dataExplorerSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('dataExplorerSubnetName'))]", + "dependsOn": [ + "vNet" + ] + }, + "blobPrivateDnsZone::blobPrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.blob.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.blob.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + } }, "dependsOn": [ - "blobEndpoint" + "blobPrivateDnsZone" ] }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", + "dfsPrivateDnsZone::dfsPrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.dfs.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.dfs.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + } }, "dependsOn": [ - "dfsEndpoint" + "dfsPrivateDnsZone" ] }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", + "queuePrivateDnsZone::queuePrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.queue.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.queue.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + } }, "dependsOn": [ - "dataFactory", - "keyVault" + "queuePrivateDnsZone" ] }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "tablePrivateDnsZone::tablePrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.table.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.table.name), '.', '-')))]", "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { + "registrationEnabled": false, "virtualNetwork": { "id": "[parameters('hub').routing.networkId]" - }, - "registrationEnabled": false + } }, "dependsOn": [ - "keyVaultPrivateDnsZone" + "tablePrivateDnsZone" ] }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "scriptEndpoint::scriptPrivateDnsZoneGroup": { + "condition": "[parameters('hub').options.privateRouting]", "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('hub').routing.scriptStorage), 'blob-endpoint-zone')]", "properties": { "privateDnsZoneConfigs": [ { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', string(parameters('hub').routing.dnsZones.blob.name))]" } } ] }, "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" + "blobPrivateDnsZone", + "scriptEndpoint" ] }, - "appTelemetry": { - "condition": "[parameters('hub').options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", - "properties": "[variables('telemetryProps')]" - }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[variables('app').dataFactory]", - "location": "[variables('app').hub.location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" - } - } - }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[variables('app').storage]", - "location": "[parameters('hub').location]", - "sku": { - "name": "[parameters('hub').options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" - }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", + "nsg": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/networkSecurityGroups", "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', variables('app').storage)]", + "name": "[variables('nsgName')]", "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/networkSecurityGroups')]", "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ + "securityRules": [ { - "name": "blobLink", + "name": "AllowVnetInBound", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "blob" - ] + "priority": 100, + "direction": "Inbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "VirtualNetwork", + "destinationAddressPrefix": "VirtualNetwork" + } + }, + { + "name": "AllowAzureLoadBalancerInBound", + "properties": { + "priority": 200, + "direction": "Inbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "AzureLoadBalancer", + "destinationAddressPrefix": "*" + } + }, + { + "name": "DenyAllInBound", + "properties": { + "priority": 4096, + "direction": "Inbound", + "access": "Deny", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "AllowVnetOutBound", + "properties": { + "priority": 100, + "direction": "Outbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "VirtualNetwork", + "destinationAddressPrefix": "VirtualNetwork" + } + }, + { + "name": "AllowInternetOutBound", + "properties": { + "priority": 200, + "direction": "Outbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "Internet" + } + }, + { + "name": "DenyAllOutBound", + "properties": { + "priority": 4096, + "direction": "Outbound", + "access": "Deny", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "*" } } ] + } + }, + "vNet": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2023-11-01", + "name": "[parameters('hub').routing.networkName]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/virtualNetworks')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[parameters('hub').options.networkAddressPrefix]" + ] + }, + "subnets": "[variables('subnets')]" }, "dependsOn": [ - "storageAccount" + "nsg" ] }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, + "blobPrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", "type": "Microsoft.Network/privateDnsZones", "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', variables('app').storage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] - }, + "dfsPrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.dfs.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, "dependsOn": [ - "storageAccount" + "vNet" ] }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[variables('app').keyVault]", + "queuePrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.queue.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] + }, + "tablePrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.table.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] + }, + "scriptStorageAccount": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('hub').routing.scriptStorage]", "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/storageAccounts')]", "properties": { - "sku": { - "name": "[parameters('hub').options.keyVaultSku]", - "family": "A" - }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ], + "supportsHttpsTrafficOnly": true, + "allowSharedKeyAccess": true, + "isHnsEnabled": false, + "minimumTlsVersion": "TLS1_2", + "allowBlobPublicAccess": false, + "publicNetworkAccess": "Enabled", "networkAcls": { "bypass": "AzureServices", - "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "id": "[parameters('hub').routing.subnets.scripts]", + "action": "Allow" + } + ] } }, "dependsOn": [ - "dataFactory" + "vNet::scriptSubnet" ] }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} - }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "scriptEndpoint": { + "condition": "[parameters('hub').options.privateRouting]", "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', variables('app').keyVault)]", + "name": "[format('{0}-blob-ep', parameters('hub').routing.scriptStorage)]", "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateEndpoints')]", "properties": { "subnet": { - "id": "[parameters('hub').routing.subnets.keyVault]" + "id": "[parameters('hub').routing.subnets.storage]" }, "privateLinkServiceConnections": [ { - "name": "keyVaultLink", + "name": "scriptLink", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('hub').routing.scriptStorage)]", "groupIds": [ - "vault" + "blob" ] } } ] }, "dependsOn": [ - "keyVault" + "scriptStorageAccount", + "vNet::scriptSubnet" ] } }, "outputs": { - "app": { - "$ref": "#/definitions/HubAppProperties", + "config": { + "$ref": "#/definitions/HubProperties", "metadata": { - "description": "FinOps hub app configuration." + "description": "FinOps hub configuration settings." }, - "value": "[variables('app')]" + "value": "[parameters('hub')]" }, - "principalId": { + "vNetId": { "type": "string", "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." + "description": "Resource ID of the virtual network." }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks', parameters('hub').routing.networkName))]" + }, + "vNetAddressSpace": { + "type": "array", + "metadata": { + "description": "Virtual network address prefixes." + }, + "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').addressSpace.addressPrefixes)]" + }, + "vNetSubnets": { + "type": "array", + "metadata": { + "description": "Virtual network subnets." + }, + "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').subnets)]" + }, + "finopsHubSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the FinOps hub network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('finopsHubSubnetName')))]" + }, + "scriptSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the script storage account network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('scriptSubnetName')))]" + }, + "dataExplorerSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Data Explorer network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('dataExplorerSubnetName')))]" } } } } }, - "configContainer": { + "appRegistration": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_Storage.ConfigContainer", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_Register", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -2955,13 +2214,16 @@ "mode": "Incremental", "parameters": { "app": { - "value": "[reference('appRegistration').outputs.app.value]" + "value": "[parameters('app')]" }, - "container": { - "value": "config" + "version": { + "value": "[variables('finOpsToolkitVersion')]" }, - "forceCreateBlobManagerIdentity": { - "value": true + "features": { + "value": [ + "DataFactory", + "Storage" + ] } }, "template": { @@ -2971,8 +2233,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "13960345490822271084" + "version": "0.39.26.7824", + "templateHash": "5436870138046688593" } }, "definitions": { @@ -3094,6 +2356,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -3120,6 +2385,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -3150,38 +2416,38 @@ } } }, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -3190,22 +2456,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -3213,194 +2478,1015 @@ } } }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." + "description": "Required. FinOps hub app getting deployed." } }, - "container": { + "version": { "type": "string", "metadata": { - "description": "Required. Name of the storage container to create or update." + "description": "Required. Version number of the FinOps hub app." } }, - "files": { - "type": "object", - "defaultValue": {}, + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." + "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." } }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, + "storageRoles": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." + "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + } + }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." } } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0}', parameters('app').id)]", + "version": "[parameters('version')]" + } + }, + "resources": [] + } + }, + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", + "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" }, "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", "properties": { - "publicAccess": "None", - "metadata": {} - } + "name": "[parameters('app').storage]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "storageAccount" + ] + }, + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", + "properties": { + "name": "[parameters('app').keyVault]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "keyVault" + ] + }, + "dataFactory::managedVirtualNetwork": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "properties": {}, + "dependsOn": [ + "dataFactory" + ] + }, + "dataFactory::managedIntegrationRuntime": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "default", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('app').hub.location]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedVirtualNetwork" + ] + }, + "dataFactory::linkedService_keyVault": { + "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "keyVault" + ] + }, + "dataFactory::linkedService_storageAccount": { + "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "storageAccount" + ] }, "storageAccount::blobService": { - "existing": true, + "condition": "[variables('usesStorage')]", "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "dfsEndpoint" + ] + }, + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('app').hub.options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } + } + }, + "storageRoleAssignments": { + "copy": { + "name": "storageRoleAssignments", + "count": "[length(variables('factoryStorageRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "storageAccount" + ] + }, + "triggerManagerIdentity": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "dependsOn": [ + "dataFactory" + ] + }, + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "triggerManagerIdentity" + ] }, "storageAccount": { - "existing": true, + "condition": "[variables('usesStorage')]", "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" + "name": "[parameters('app').storage]", + "location": "[parameters('app').hub.location]", + "sku": { + "name": "[parameters('app').hub.options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Identity', deployment().name)]", + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + }, + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "roles": { - "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" - ] - } + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "4534337491931150093" + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "blob" + ] } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('app').hub.options.keyVaultSku]", + "family": "A" + }, + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + } + }, + "dependsOn": [ + "dataFactory" + ] + }, + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('app').keyVault)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.keyVault]" + }, + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } + ] + }, + "dependsOn": [ + "keyVault" + ] + }, + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" } - }, + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", + "getStoragePrivateEndpointConnections", + "keyVault" + ] + }, + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "_1.HubRoutingProperties": { - "type": "object", + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections", + "keyVault" + ] + }, + "getStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", + "stopTriggers", + "storageAccount" + ] + }, + "approveStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getStoragePrivateEndpointConnections", + "storageAccount" + ] + }, + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('app').dataFactory]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" }, "scriptStorage": { "type": "string" @@ -3425,6 +3511,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -3451,6 +3540,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -3484,34 +3574,20 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { "type": "string" }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } + "suffix": { + "type": "string" }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "tags": { + "type": "object" }, "dataFactory": { "type": "string" @@ -3521,22 +3597,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -3544,629 +3619,229 @@ } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the identity is associated with." + "description": "Required. FinOps hub app the deployment script is being run for." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the user assigned identity." + "description": "Required. Name of the managed identity to create." } }, - "roleAssignmentResourceId": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." } }, - "roles": { + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/EnvironmentVariable" }, + "defaultValue": [], "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." + "description": "Optional. Environment variables to use for the deployment script." } } }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, "resources": { "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", "location": "[parameters('app').hub.location]" }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" + "count": "[length(variables('privateEndpointDeploymentRoles'))]" }, + "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } }, - "value": "[reference('identity').principalId]" + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } - } + }, + "dependsOn": [ + "appTelemetry", + "dataFactory", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" + ] + } + }, + "outputs": { + "dataFactoryId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Data Factory instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Upload', deployment().name)]", + "keyVaultId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Key Vault instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + }, + "triggerManagerIdentityName": { + "type": "string", + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + } + } + } + }, + "dependsOn": [ + "infrastructure" + ] + }, + "configContainer": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_Storage.ConfigContainer", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "container": { + "value": "[variables('CONFIG')]" + }, + "forceCreateBlobManagerIdentity": { + "value": true + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "7314877606184110283" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - } + "name": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "keyVaultSku": { + "type": "string" }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "networkAddressPrefix": { + "type": "string" }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "containerName": { - "type": "string", - "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" - }, - "filesUploaded": { - "type": "int", - "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" - }, - "identityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" - }, - "identityPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" - } - } - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "ingestionContainer": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_Storage.IngestionContainer", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[reference('appRegistration').outputs.app.value]" - }, - "container": { - "value": "ingestion" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "13960345490822271084" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" + "privateRouting": { + "type": "bool" }, "publisherIsolation": { "type": "bool" @@ -4250,6 +3925,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -4276,6 +3954,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -4309,35 +3988,21 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -4346,22 +4011,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -4398,7 +4062,7 @@ } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", "fileCount": "[length(items(parameters('files')))]", "hasFiles": "[greater(variables('fileCount'), 0)]" }, @@ -4427,7 +4091,7 @@ "identity": { "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}.Identity', deployment().name)]", "properties": { "expressionEvaluationOptions": { @@ -4458,8 +4122,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "4534337491931150093" + "version": "0.39.26.7824", + "templateHash": "2980528181281411934" } }, "definitions": { @@ -4581,6 +4245,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -4607,6 +4274,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -4640,35 +4308,21 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -4677,22 +4331,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -4704,7 +4357,7 @@ { "namespace": "__bicep", "members": { - "getPublisherTags": { + "getAppPublisherTags": { "parameters": [ { "$ref": "#/definitions/HubAppProperties", @@ -4717,7 +4370,7 @@ ], "output": { "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" }, "metadata": { "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", @@ -4763,7 +4416,7 @@ "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", "location": "[parameters('app').hub.location]" }, "identityRoleAssignments": { @@ -4813,7 +4466,7 @@ "uploadFiles": { "condition": "[variables('hasFiles')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", + "apiVersion": "2025-04-01", "name": "[format('{0}.Upload', deployment().name)]", "properties": { "expressionEvaluationOptions": { @@ -4854,8 +4507,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" } }, "definitions": { @@ -4988,6 +4641,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -5014,6 +4670,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -5047,35 +4704,21 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -5084,22 +4727,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -5153,7 +4795,8 @@ }, "variables": { "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" }, "resources": { "identity": { @@ -5202,7 +4845,7 @@ "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} } }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", "dependsOn": [ "identity", "identityRoleAssignments" @@ -5259,10 +4902,10 @@ "appRegistration" ] }, - "uploadSettings": { + "ingestionContainer": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_Storage.UpdateSettings", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_Storage.IngestionContainer", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -5270,52 +4913,10 @@ "mode": "Incremental", "parameters": { "app": { - "value": "[reference('appRegistration').outputs.app.value]" - }, - "identityName": { - "value": "[reference('configContainer').outputs.identityName.value]" - }, - "scriptName": { - "value": "[format('{0}_uploadSettings', reference('appRegistration').outputs.app.value.storage)]" - }, - "environmentVariables": { - "value": [ - { - "name": "ftkVersion", - "value": "[variables('$fxv#1')]" - }, - { - "name": "scopes", - "value": "[join(parameters('scopesToMonitor'), '|')]" - }, - { - "name": "msexportRetentionInDays", - "value": "[string(parameters('msexportRetentionInDays'))]" - }, - { - "name": "ingestionRetentionInMonths", - "value": "[string(parameters('ingestionRetentionInMonths'))]" - }, - { - "name": "rawRetentionInDays", - "value": "[string(parameters('rawRetentionInDays'))]" - }, - { - "name": "finalRetentionInMonths", - "value": "[string(parameters('finalRetentionInMonths'))]" - }, - { - "name": "storageAccountName", - "value": "[reference('appRegistration').outputs.app.value.storage]" - }, - { - "name": "containerName", - "value": "config" - } - ] + "value": "[parameters('app')]" }, - "scriptContent": { - "value": "[variables('$fxv#2')]" + "container": { + "value": "[variables('INGESTION')]" } }, "template": { @@ -5325,22 +4926,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" + "version": "0.39.26.7824", + "templateHash": "7314877606184110283" } }, "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "_1.HubProperties": { "type": "object", "properties": { @@ -5459,6 +5049,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -5485,6 +5078,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -5518,35 +5112,21 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -5555,22 +5135,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -5582,2756 +5161,437 @@ "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + "description": "Required. FinOps hub app that storage is getting updated for." } }, - "scriptContent": { + "container": { "type": "string", "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Required. Name of the storage container to create or update." } }, - "arguments": { - "type": "string", - "defaultValue": "", + "files": { + "type": "object", + "defaultValue": {}, "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." } }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." } } }, "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" }, "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "properties": { + "publicAccess": "None", + "metadata": {} + } }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", + "storageAccount::blobService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + }, + "storageAccount": { "existing": true, "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "name": "[parameters('app').storage]" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Identity', deployment().name)]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "appRegistration", - "configContainer" - ] - } - }, - "outputs": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Name of the Data Factory." - }, - "value": "[reference('appRegistration').outputs.app.value.dataFactory]" - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." - }, - "value": "[reference('appRegistration').outputs.app.value.storage]" - }, - "configContainer": { - "type": "string", - "metadata": { - "description": "The name of the container used for configuration settings." - }, - "value": "[reference('configContainer').outputs.containerName.value]" - }, - "ingestionContainer": { - "type": "string", - "metadata": { - "description": "The name of the container used for normalized data ingestion." - }, - "value": "[reference('ingestionContainer').outputs.containerName.value]" - }, - "storageUrlForPowerBI": { - "type": "string", - "metadata": { - "description": "URL to use when connecting custom Power BI reports to your data." - }, - "value": "[format('https://{0}.dfs.{1}/{2}', reference('appRegistration').outputs.app.value.storage, environment().suffixes.storage, reference('ingestionContainer').outputs.containerName.value)]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Object ID of the Data Factory managed identity. This will be needed when configuring managed exports." - }, - "value": "[reference('appRegistration').outputs.principalId.value]" - }, - "publisherTags": { - "type": "object", - "metadata": { - "description": "Tags for the FinOps hub publisher." - }, - "value": "[reference('appRegistration').outputs.app.value.publisher.tags]" - } - } - } - }, - "dependsOn": [ - "infrastructure" - ] - }, - "cmExports": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.CostManagement.Exports", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[variables('hub')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "12652260421176486151" - } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance to deploy the app to." - } - } - }, - "variables": { - "$fxv#0": "12.0", - "$fxv#1": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"Date\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectivePrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Cost\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceLocation\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedService\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo1\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo2\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AdditionalInfo\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSection\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceGroup\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PlanName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeType\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Frequency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherType\"}\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#10": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Kind\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Kind\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservedHours\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ReservedHours\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TotalReservedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"TotalReservedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"UsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsedHours\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UsedHours\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#11": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"SKU\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SKU\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Location\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CostWithNoReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostWithNoReservedInstances\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"FirstUsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"FirstUsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"LookBackPeriod\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"NetSavings\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"NetSavings\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"NormalizedSize\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NormalizedSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RecommendedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RecommendedQuantityNormalized\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Scope\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Scope\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuProperties\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuProperties\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TotalCostWithReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TotalCostWithReservedInstances\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#12": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"Cost With No ReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostWithNoReservedInstancesJson\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"First UsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"FirstUsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Instance Flexibility Ratio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Instance Flexibility Group\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Location\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"LookBackPeriod\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Net Savings\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NetSavingsJson\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Normalized Size\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NormalizedSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Recommended Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Recommended Quantity Normalized\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"scope\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Scope\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Sku Properties\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuProperties\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Total Cost With ReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TotalCostWithReservedInstancesJson\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#13": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"AccountOwnerEmail\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerEmail\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Amount\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ArmSkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingMonth\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingMonth\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CurrentEnrollmentId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CurrentEnrollmentId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"DepartmentName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"DepartmentName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Description\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EventDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EventType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MonetaryCommitment\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MonetaryCommitment\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Overage\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Overage\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingEnrollment\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingEnrollment\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#14": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Amount\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ArmSkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Description\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EventDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EventType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Invoice\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Invoice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#2": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"Date\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectivePrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Cost\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceLocation\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedService\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo1\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo2\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AdditionalInfo\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSection\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceGroup\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PlanName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeType\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Frequency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherType\"}\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#3": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuMeter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AmortizationClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ServiceModel\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPlanName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#4": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuMeter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AmortizationClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ServiceModel\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPlanName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#5": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#6": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#7": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UsageQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UsageUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ChargeId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ChargeId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#8": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"EnrollmentNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EnrollmentNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProductID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveStartDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveEndDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BasePrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MarketPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CurrencyCode\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CurrencyCode\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"IncludedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"IncludedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"OfferID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PriceType\" }\r\n }\r\n ]\r\n }\r\n}\r\n", - "$fxv#9": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProductId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TierMinimumUnits\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TierMinimumUnits\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveStartDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveEndDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BasePrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MarketPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PriceType\" }\r\n }\r\n ]\r\n }\r\n}\r\n" - }, - "resources": { - "appRegistration": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.CostManagement.Exports_Register", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[parameters('hub')]" - }, - "publisher": { - "value": "Microsoft FinOps hubs" - }, - "namespace": { - "value": "Microsoft.FinOpsHubs" - }, - "appName": { - "value": "Core" - }, - "displayName": { - "value": "FinOps hub core" - }, - "appVersion": { - "value": "[variables('$fxv#0')]" - }, - "features": { - "value": [ - "DataFactory", - "Storage" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "15179190433979236138" - } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppFeature": { - "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], - "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - }, - { - "type": "bool", - "nullable": true, - "name": "forceAppTags" - } - ], - "output": { - "type": "object", - "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "getPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "newApp": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "appPartialName" - }, - { - "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" - } - ], - "output": { - "$ref": "#/definitions/HubAppProperties", - "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" - }, - "metadata": { - "description": "Creates a new FinOps hub app configuration object.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - }, - { - "namespace": "_1", - "members": { - "newAppInternal": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherSuffix" - }, - { - "type": "object", - "name": "publisherTags" - }, - { - "type": "string", - "name": "appName" - }, - { - "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" - } - ], - "output": { - "$ref": "#/definitions/HubAppProperties", - "value": { - "name": "[parameters('appName')]", - "displayName": "[parameters('appDisplayName')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", - "publisher": { - "name": "[parameters('publisherName')]", - "displayName": "[parameters('publisherDisplayName')]", - "suffix": "[parameters('publisherSuffix')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" - }, - "hub": "[parameters('hub')]", - "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "safeStorageName": { - "parameters": [ - { - "type": "string", - "name": "name" - } - ], - "output": { - "type": "string", - "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance properties." - } - }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app publisher." - } - }, - "namespace": { - "type": "string", - "metadata": { - "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - }, - "appName": { - "type": "string", - "metadata": { - "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - }, - "displayName": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app." - } - }, - "appVersion": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Version number of the FinOps hub app." - } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." - } - }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - } - }, - "variables": { - "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", - "version": "[parameters('appVersion')]" - } - }, - "resources": [] - } - }, - "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" - }, - "resources": { - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', variables('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] - }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] - }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] - }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] - }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] - }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" - } - } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] - }, - "appTelemetry": { - "condition": "[parameters('hub').options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", - "properties": "[variables('telemetryProps')]" - }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[variables('app').dataFactory]", - "location": "[variables('app').hub.location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" - } - } - }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[variables('app').storage]", - "location": "[parameters('hub').location]", - "sku": { - "name": "[parameters('hub').options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" - }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', variables('app').storage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "blob" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" - }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', variables('app').storage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[variables('app').keyVault]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", - "properties": { - "sku": { - "name": "[parameters('hub').options.keyVaultSku]", - "family": "A" - }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" - } - }, - "dependsOn": [ - "dataFactory" - ] - }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} - }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', variables('app').keyVault)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.keyVault]" - }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] - }, - "dependsOn": [ - "keyVault" - ] - } - }, - "outputs": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "FinOps hub app configuration." - }, - "value": "[variables('app')]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - } - } - } - } - }, - "schemaFiles": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.CostManagement.Exports_Storage.SchemaFiles", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[reference('appRegistration').outputs.app.value]" - }, - "container": { - "value": "config" - }, - "files": { - "value": { - "schemas/actualcost_c360-2025-04.json": "[variables('$fxv#1')]", - "schemas/amortizedcost_c360-2025-04.json": "[variables('$fxv#2')]", - "schemas/focuscost_1.2.json": "[variables('$fxv#3')]", - "schemas/focuscost_1.2-preview.json": "[variables('$fxv#4')]", - "schemas/focuscost_1.0r2.json": "[variables('$fxv#5')]", - "schemas/focuscost_1.0.json": "[variables('$fxv#6')]", - "schemas/focuscost_1.0-preview(v1).json": "[variables('$fxv#7')]", - "schemas/pricesheet_2023-05-01_ea.json": "[variables('$fxv#8')]", - "schemas/pricesheet_2023-05-01_mca.json": "[variables('$fxv#9')]", - "schemas/reservationdetails_2023-03-01.json": "[variables('$fxv#10')]", - "schemas/reservationrecommendations_2023-05-01_ea.json": "[variables('$fxv#11')]", - "schemas/reservationrecommendations_2023-05-01_mca.json": "[variables('$fxv#12')]", - "schemas/reservationtransactions_2023-05-01_ea.json": "[variables('$fxv#13')]", - "schemas/reservationtransactions_2023-05-01_mca.json": "[variables('$fxv#14')]" - } - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "13960345490822271084" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." - } - }, - "container": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage container to create or update." - } - }, - "files": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." - } - }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" - }, - "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", - "properties": { - "publicAccess": "None", - "metadata": {} - } - }, - "storageAccount::blobService": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" - }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Identity', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "roles": { - "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "4534337491931150093" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the identity is associated with." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the user assigned identity." - } - }, - "roleAssignmentResourceId": { - "type": "string", - "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." - } - }, - "roles": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." - } - } - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", - "location": "[parameters('app').hub.location]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." - }, - "value": "[reference('identity').principalId]" - } - } - } - } - }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Upload', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + "identityName": { + "value": "[format('{0}_blobManager', parameters('app').storage)]" }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "containerName": { - "type": "string", - "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" - }, - "filesUploaded": { - "type": "int", - "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" - }, - "identityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" - }, - "identityPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" - } - } - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "exportContainer": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.CostManagement.Exports_Storage.ExportContainer", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[reference('appRegistration').outputs.app.value]" - }, - "container": { - "value": "msexports" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "13960345490822271084" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "roles": { + "value": [ + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" + ] + } }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2980528181281411934" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the identity is associated with." + } }, - "keyVault": { - "type": "string" + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the user assigned identity." + } }, - "scripts": { - "type": "string" + "roleAssignmentResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource access is being granted for." + } }, - "storage": { - "type": "string" + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of RBAC role assignment GUIDs." + } } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "location": "[parameters('app').hub.location]" }, - "displayName": { - "type": "string" + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" }, - "suffix": { - "type": "string" + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." + }, + "value": "[parameters('identityName')]" }, - "tags": { - "type": "object" + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" } } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" } } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." - } - }, - "container": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage container to create or update." - } - }, - "files": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." - } - }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" - }, - "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", - "properties": { - "publicAccess": "None", - "metadata": {} - } - }, - "storageAccount::blobService": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "uploadFiles": { + "condition": "[variables('hasFiles')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Identity', deployment().name)]", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Upload', deployment().name)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -8342,16 +5602,26 @@ "value": "[parameters('app')]" }, "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + "value": "[reference('identity').outputs.name.value]" }, - "roles": { + "environmentVariables": { "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" + }, + { + "name": "files", + "value": "[string(parameters('files'))]" + } ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" } }, "template": { @@ -8361,11 +5631,22 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "4534337491931150093" + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" } }, "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "_1.HubProperties": { "type": "object", "properties": { @@ -8484,6 +5765,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -8510,6 +5794,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -8543,35 +5828,21 @@ "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -8580,22 +5851,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -8603,3437 +5873,7014 @@ } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the identity is associated with." + "description": "Required. FinOps hub app the deployment script is being run for." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the user assigned identity." + "description": "Required. Name of the managed identity to create." } }, - "roleAssignmentResourceId": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." } }, - "roles": { + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/EnvironmentVariable" }, + "defaultValue": [], "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." + "description": "Optional. Environment variables to use for the deployment script." } } }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, "resources": { "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", "location": "[parameters('app').hub.location]" }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" + "count": "[length(variables('privateEndpointDeploymentRoles'))]" }, + "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } }, - "value": "[reference('identity').principalId]" + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" + }, + "filesUploaded": { + "type": "int", + "metadata": { + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" + }, + "identityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + } + } + } + }, + "dependsOn": [ + "appRegistration" + ] + }, + "uploadSettings": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_Storage.UpdateSettings", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[reference('configContainer').outputs.identityName.value]" + }, + "scriptName": { + "value": "[format('{0}_uploadSettings', parameters('app').storage)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "environmentVariables": { + "value": [ + { + "name": "ftkVersion", + "value": "[variables('finOpsToolkitVersion')]" + }, + { + "name": "scopes", + "value": "[join(parameters('scopesToMonitor'), '|')]" + }, + { + "name": "msexportRetentionInDays", + "value": "[string(parameters('msexportRetentionInDays'))]" + }, + { + "name": "ingestionRetentionInMonths", + "value": "[string(parameters('ingestionRetentionInMonths'))]" + }, + { + "name": "rawRetentionInDays", + "value": "[string(parameters('rawRetentionInDays'))]" + }, + { + "name": "finalRetentionInMonths", + "value": "[string(parameters('finalRetentionInMonths'))]" + }, + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "config" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } } }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[format('{0}.Upload', deployment().name)]", + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - } + "name": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "keyVaultSku": { + "type": "string" }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } + "networkAddressPrefix": { + "type": "string" }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } + "privateRouting": { + "type": "bool" }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } + "publisherIsolation": { + "type": "bool" }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } + "storageInfrastructureEncryption": { + "type": "bool" }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "dataFactory": { + "type": "string" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] + "keyVault": { + "type": "string" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" } } } }, - "dependsOn": [ - "identity" - ] + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } }, - "outputs": { - "containerName": { - "type": "string", + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - "filesUploaded": { - "type": "int", + "identityName": { + "type": "string", "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" + "description": "Required. Name of the managed identity to create." + } }, - "identityId": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } }, - "identityName": { + "scriptContent": { "type": "string", "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + "description": "Required. Name of the deployment script to create." + } }, - "identityPrincipalId": { + "arguments": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } }, "dependsOn": [ - "appRegistration" + "appRegistration", + "configContainer" ] } }, "outputs": { - "exportContainer": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Properties of the hub app." + }, + "value": "[parameters('app')]" + }, + "dataFactoryName": { "type": "string", "metadata": { - "description": "Name of the container used for Cost Management exports." + "description": "Name of the Data Factory." }, - "value": "[reference('exportContainer').outputs.containerName.value]" + "value": "[parameters('app').dataFactory]" }, - "schemaFilesUploaded": { - "type": "int", + "storageAccountName": { + "type": "string", "metadata": { - "description": "Number of schema files uploaded." + "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." }, - "value": "[reference('schemaFiles').outputs.filesUploaded.value]" + "value": "[parameters('app').storage]" + }, + "storageUrlForPowerBI": { + "type": "string", + "metadata": { + "description": "URL to use when connecting custom Power BI reports to your data." + }, + "value": "[format('https://{0}.dfs.{1}/{2}', parameters('app').storage, environment().suffixes.storage, variables('INGESTION'))]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Object ID of the Data Factory managed identity. This will be needed when configuring managed exports." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + }, + "triggerManagerIdentityName": { + "type": "string", + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[reference('appRegistration').outputs.triggerManagerIdentityName.value]" } } } - }, - "dependsOn": [ - "core" - ] + } }, - "dataExplorer": { - "condition": "[variables('deployDataExplorer')]", + "cmExports": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "dataExplorer", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.Exports", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[parameters('dataExplorerName')]" - }, - "clusterSku": { - "value": "[parameters('dataExplorerSku')]" - }, - "clusterCapacity": { - "value": "[parameters('dataExplorerCapacity')]" - }, - "location": { - "value": "[parameters('location')]" - }, - "tags": { - "value": "[variables('hub').tags]" - }, - "tagsByResource": { - "value": "[parameters('tagsByResource')]" - }, - "dataFactoryName": { - "value": "[reference('core').outputs.dataFactoryName.value]" - }, - "rawRetentionInDays": { - "value": "[parameters('dataExplorerRawRetentionInDays')]" - }, - "virtualNetworkId": "[if(parameters('enablePublicAccess'), createObject('value', ''), createObject('value', reference('infrastructure').outputs.vNetId.value))]", - "privateEndpointSubnetId": "[if(parameters('enablePublicAccess'), createObject('value', ''), createObject('value', reference('infrastructure').outputs.dataExplorerSubnetId.value))]", - "enablePublicAccess": { - "value": "[parameters('enablePublicAccess')]" - }, - "storageAccountName": { - "value": "[reference('core').outputs.storageAccountName.value]" + "mode": "Incremental", + "parameters": { + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft.CostManagement', 'Exports')]" } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "12711851392414163333" + "version": "0.39.26.7824", + "templateHash": "12146592525418089958" } }, - "parameters": { - "clusterName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics. If empty, Azure Data Explorer will not be deployed. Required to use with Power BI if you have more than $2-5M/mo in costs being monitored. Default: \"\" (do not use)." - } - }, - "clusterSku": { - "type": "string", - "defaultValue": "Dev(No SLA)_Standard_E2a_v4", - "allowedValues": [ - "Dev(No SLA)_Standard_E2a_v4", - "Dev(No SLA)_Standard_D11_v2", - "Standard_D11_v2", - "Standard_D12_v2", - "Standard_D13_v2", - "Standard_D14_v2", - "Standard_D16d_v5", - "Standard_D32d_v4", - "Standard_D32d_v5", - "Standard_DS13_v2+1TB_PS", - "Standard_DS13_v2+2TB_PS", - "Standard_DS14_v2+3TB_PS", - "Standard_DS14_v2+4TB_PS", - "Standard_E2a_v4", - "Standard_E2ads_v5", - "Standard_E2d_v4", - "Standard_E2d_v5", - "Standard_E4a_v4", - "Standard_E4ads_v5", - "Standard_E4d_v4", - "Standard_E4d_v5", - "Standard_E8a_v4", - "Standard_E8ads_v5", - "Standard_E8as_v4+1TB_PS", - "Standard_E8as_v4+2TB_PS", - "Standard_E8as_v5+1TB_PS", - "Standard_E8as_v5+2TB_PS", - "Standard_E8d_v4", - "Standard_E8d_v5", - "Standard_E8s_v4+1TB_PS", - "Standard_E8s_v4+2TB_PS", - "Standard_E8s_v5+1TB_PS", - "Standard_E8s_v5+2TB_PS", - "Standard_E16a_v4", - "Standard_E16ads_v5", - "Standard_E16as_v4+3TB_PS", - "Standard_E16as_v4+4TB_PS", - "Standard_E16as_v5+3TB_PS", - "Standard_E16as_v5+4TB_PS", - "Standard_E16d_v4", - "Standard_E16d_v5", - "Standard_E16s_v4+3TB_PS", - "Standard_E16s_v4+4TB_PS", - "Standard_E16s_v5+3TB_PS", - "Standard_E16s_v5+4TB_PS", - "Standard_E64i_v3", - "Standard_E80ids_v4", - "Standard_EC8ads_v5", - "Standard_EC8as_v5+1TB_PS", - "Standard_EC8as_v5+2TB_PS", - "Standard_EC16ads_v5", - "Standard_EC16as_v5+3TB_PS", - "Standard_EC16as_v5+4TB_PS", - "Standard_L4s", - "Standard_L8as_v3", - "Standard_L8s", - "Standard_L8s_v2", - "Standard_L8s_v3", - "Standard_L16as_v3", - "Standard_L16s", - "Standard_L16s_v2", - "Standard_L16s_v3", - "Standard_L32as_v3", - "Standard_L32s_v3" - ], - "metadata": { - "description": "Optional. Name of the Azure Data Explorer SKU. Default: \"Dev(No SLA)_Standard_E2a_v4\"." - } - }, - "clusterCapacity": { - "type": "int", - "defaultValue": 1, - "minValue": 1, - "maxValue": 1000, - "metadata": { - "description": "Optional. Number of nodes to use in the cluster. Allowed values: 1 for the Basic SKU tier and 2-1000 for Standard. Default: 1 for dev/test SKUs, 2 for standard SKUs." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } } }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Optional. Azure location to use for the managed identity and deployment script to auto-start triggers. Default: (resource group location)." + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } } }, - "tags": { + "_1.IdNameObject": { "type": "object", - "defaultValue": {}, + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, "metadata": { - "description": "Optional. Tags to apply to all resources." + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } } }, - "tagsByResource": { + "HubAppProperties": { "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." - } - }, - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory instance." - } - }, - "rawRetentionInDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account to use for data ingestion." - } - }, - "virtualNetworkId": { - "type": "string", - "metadata": { - "description": "Required. Resource ID of the virtual network for private endpoints." - } - }, - "privateEndpointSubnetId": { - "type": "string", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, "metadata": { - "description": "Required. Resource ID of the subnet for private endpoints." + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } } - }, - "enablePublicAccess": { - "type": "bool", + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Optional. Enable public access." + "description": "Required. FinOps hub app getting deployed." } } }, "variables": { - "$fxv#0": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_1(id: string) {\r\n dynamic({\r\n \"arizeai.observabilityeval/organizations\": { \"SingularDisplayName\": \"Azure Native Arize AI Cloud Service\" }\r\n ,\"astronomer.astro/organizations\": { \"SingularDisplayName\": \"Astro Organization\" }\r\n ,\"citrix.services/xenappessentials\": { \"SingularDisplayName\": \"Citrix Virtual Apps Essentials\" }\r\n ,\"citrix.services/xendesktopessentials\": { \"SingularDisplayName\": \"Citrix Virtual Desktops Essentials\" }\r\n ,\"commvault.contentstore/cloudaccounts\": { \"SingularDisplayName\": \"Commvault Cloud Account\" }\r\n ,\"commvault.contentstore/cloudaccounts/plans\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts plan\" }\r\n ,\"commvault.contentstore/cloudaccounts/protectiongroups\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection group\" }\r\n ,\"commvault.contentstore/cloudaccounts/protectiongroups/protecteditems\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection groups protected item\" }\r\n ,\"commvault.contentstore/cloudaccounts/storages\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts storage\" }\r\n ,\"dell.storage/filesystems\": { \"SingularDisplayName\": \"Dell PowerScale\" }\r\n ,\"dynatrace.observability/monitors\": { \"SingularDisplayName\": \"Dynatrace\" }\r\n ,\"github.network/networksettings\": { \"SingularDisplayName\": \"GitHub.Network network setting\" }\r\n ,\"informatica.datamanagement/organizations\": { \"SingularDisplayName\": \"Informatica Organization\" }\r\n ,\"lambdatest.hyperexecute/organizations\": { \"SingularDisplayName\": \"Azure Native LambdaTest - HyperExecute Cloud Service\" }\r\n ,\"microsoft.aad/domainservices\": { \"SingularDisplayName\": \"Microsoft Entra Domain Services\" }\r\n ,\"microsoft.aadiam/diagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.aadiam diagnostic setting\" }\r\n ,\"microsoft.aadiam/privatelinkforazuread\": { \"SingularDisplayName\": \"Private Link for Microsoft Entra ID\" }\r\n ,\"microsoft.advisor/advisorscore\": { \"SingularDisplayName\": \"Microsoft.Advisor advisor score\" }\r\n ,\"microsoft.advisor/assessments\": { \"SingularDisplayName\": \"Microsoft.Advisor assessment\" }\r\n ,\"microsoft.advisor/configurations\": { \"SingularDisplayName\": \"Microsoft.Advisor configuration\" }\r\n ,\"microsoft.advisor/generaterecommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor generate recommendation\" }\r\n ,\"microsoft.advisor/metadata\": { \"SingularDisplayName\": \"Microsoft.Advisor metadata\" }\r\n ,\"microsoft.advisor/recommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendation\" }\r\n ,\"microsoft.advisor/recommendations/suppressions\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendations suppression\" }\r\n ,\"microsoft.advisor/resiliencyreviews\": { \"SingularDisplayName\": \"Microsoft.Advisor resiliency review\" }\r\n ,\"microsoft.agfoodplatform/farmbeats\": { \"SingularDisplayName\": \"Azure Data Manager for Agriculture\" }\r\n ,\"microsoft.agfoodplatform/farmbeatsextensiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats extension definition\" }\r\n ,\"microsoft.agfoodplatform/farmbeatssolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats solution definition\" }\r\n ,\"microsoft.agricultureplatform/agriservices\": { \"SingularDisplayName\": \"Agriculture data solutions\" }\r\n ,\"microsoft.akshybrid/agentpools\": { \"SingularDisplayName\": \"Microsoft.AksHybrid agent pool\" }\r\n ,\"microsoft.akshybrid/provisionedclusters\": { \"SingularDisplayName\": \"Microsoft.AksHybrid provisioned cluster\" }\r\n ,\"microsoft.akshybrid/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.AksHybrid upgrade profile\" }\r\n ,\"microsoft.alertsmanagement/actionrules\": { \"SingularDisplayName\": \"Alert processing rule\" }\r\n ,\"microsoft.alertsmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alert\" }\r\n ,\"microsoft.alertsmanagement/alerts/enrichments\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alerts enrichment\" }\r\n ,\"microsoft.alertsmanagement/prometheusrulegroups\": { \"SingularDisplayName\": \"Prometheus rule group\" }\r\n ,\"microsoft.alertsmanagement/smartdetectoralertrules\": { \"SingularDisplayName\": \"Smart detector alert rule\" }\r\n ,\"microsoft.alertsmanagement/smartgroups\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement smart group\" }\r\n ,\"microsoft.alertsmanagement/tenantactivitylogalerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement tenant activity log alert\" }\r\n ,\"microsoft.all/arcvirtualmachines\": { \"SingularDisplayName\": \"Azure Arc virtual machine\" }\r\n ,\"microsoft.all/hcivirtualmachines\": { \"SingularDisplayName\": \"Azure Local Virtual Machine - Azure Arc\" }\r\n ,\"microsoft.all/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.analysisservices/servers\": { \"SingularDisplayName\": \"Analysis Services server\" }\r\n ,\"microsoft.anybuild/clusters\": { \"SingularDisplayName\": \"AnyBuild cluster\" }\r\n ,\"microsoft.apicenter/deletedservices\": { \"SingularDisplayName\": \"Microsoft.ApiCenter deleted service\" }\r\n ,\"microsoft.apicenter/services\": { \"SingularDisplayName\": \"API Center\" }\r\n ,\"microsoft.apicenter/services/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.apimanagement/gateways\": { \"SingularDisplayName\": \"API Management gateway\" }\r\n ,\"microsoft.apimanagement/gateways/configconnections\": { \"SingularDisplayName\": \"Microsoft.ApiManagement gateways config connection\" }\r\n ,\"microsoft.apimanagement/service\": { \"SingularDisplayName\": \"API Management service\" }\r\n ,\"microsoft.apimanagement/service/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.apisecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.ApiSecurity defender setting\" }\r\n ,\"microsoft.app/agents\": { \"SingularDisplayName\": \"SRE Agent\" }\r\n ,\"microsoft.app/builders\": { \"SingularDisplayName\": \"Microsoft.App builder\" }\r\n ,\"microsoft.app/builders/builds\": { \"SingularDisplayName\": \"Microsoft.App builders build\" }\r\n ,\"microsoft.app/connectedenvironments\": { \"SingularDisplayName\": \"Container Apps Connected Environment\" }\r\n ,\"microsoft.app/containerapps\": { \"SingularDisplayName\": \"Container App\" }\r\n ,\"microsoft.app/jobs\": { \"SingularDisplayName\": \"Container App Job\" }\r\n ,\"microsoft.app/logicapps\": { \"SingularDisplayName\": \"Logic app\" }\r\n ,\"microsoft.app/logicapps/workflows\": { \"SingularDisplayName\": \"Logic app workflow\" }\r\n ,\"microsoft.app/managedenvironments\": { \"SingularDisplayName\": \"Container Apps Environment\" }\r\n ,\"microsoft.app/sessionpools\": { \"SingularDisplayName\": \"Container App Session Pool\" }\r\n ,\"microsoft.app/spaces\": { \"SingularDisplayName\": \"App Space\" }\r\n ,\"microsoft.appassessment/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate project\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessment\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedapplications\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed application\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed machine\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/machinestoassess\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments machines to asses\" }\r\n ,\"microsoft.appassessment/migrateprojects/sites\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects site\" }\r\n ,\"microsoft.appassessment/migrateprojects/sites/applianceconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects sites appliance configuration\" }\r\n ,\"microsoft.appcomplianceautomation/reports\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation report\" }\r\n ,\"microsoft.appcomplianceautomation/reports/evidences\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports evidence\" }\r\n ,\"microsoft.appcomplianceautomation/reports/scopingconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports scoping configuration\" }\r\n ,\"microsoft.appcomplianceautomation/reports/snapshots\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshot\" }\r\n ,\"microsoft.appcomplianceautomation/reports/snapshots/controls\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshots control\" }\r\n ,\"microsoft.appcomplianceautomation/reports/webhooks\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports webhook\" }\r\n ,\"microsoft.appconfiguration/configurationstores\": { \"SingularDisplayName\": \"App Configuration\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hub\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs/applications\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs application\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs/applications/members\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs applications member\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsite\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites agent\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqldatabases\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqldatabase\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqlinstances\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqlinstance\" }\r\n ,\"microsoft.appplatform/spring\": { \"SingularDisplayName\": \"Azure Spring Apps\" }\r\n ,\"microsoft.appsecurity/appprotectmanagedrulesetmanifests\": { \"SingularDisplayName\": \"Microsoft.AppSecurity app protect managed rule set manifest\" }\r\n ,\"microsoft.appsecurity/policies\": { \"SingularDisplayName\": \"App Protect Policy\" }\r\n ,\"microsoft.arc/all\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\r\n ,\"microsoft.arc/allfairfax\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\r\n ,\"microsoft.arc/kubernetesresources\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\r\n ,\"microsoft.arc/kubernetesresourcesfairfax\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\r\n ,\"microsoft.arcnetworking/arcnwloadbalancers\": { \"SingularDisplayName\": \"Microsoft.ArcNetworking arc nw load balancer\" }\r\n ,\"microsoft.aszlabhardware/labservers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware labserver\" }\r\n ,\"microsoft.aszlabhardware/reservations\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservation\" }\r\n ,\"microsoft.aszlabhardware/reservations/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservations server\" }\r\n ,\"microsoft.aszlabhardware/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware server\" }\r\n ,\"microsoft.attestation/attestationproviders\": { \"SingularDisplayName\": \"Attestation provider\" }\r\n ,\"microsoft.authorization/accessreviewhistorydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review history definition\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definition\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instance\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances/decisions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instances decision\" }\r\n ,\"microsoft.authorization/accessreviewschedulesettings\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule setting\" }\r\n ,\"microsoft.authorization/datapolicymanifests\": { \"SingularDisplayName\": \"Microsoft.Authorization data policy manifest\" }\r\n ,\"microsoft.authorization/denyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization deny assignment\" }\r\n ,\"microsoft.authorization/locks\": { \"SingularDisplayName\": \"Microsoft.Authorization lock\" }\r\n ,\"microsoft.authorization/policyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization policy assignment\" }\r\n ,\"microsoft.authorization/policydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definition\" }\r\n ,\"microsoft.authorization/policydefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definitions version\" }\r\n ,\"microsoft.authorization/policyexemptions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy exemption\" }\r\n ,\"microsoft.authorization/policysetdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definition\" }\r\n ,\"microsoft.authorization/policysetdefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definitions version\" }\r\n ,\"microsoft.authorization/privatelinkassociations\": { \"SingularDisplayName\": \"Microsoft.Authorization private link association\" }\r\n ,\"microsoft.authorization/provideroperations\": { \"SingularDisplayName\": \"Microsoft.Authorization provider operation\" }\r\n ,\"microsoft.authorization/resourcemanagementprivatelinks\": { \"SingularDisplayName\": \"Resource management private link\" }\r\n ,\"microsoft.authorization/roleassignmentapprovals\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approval\" }\r\n ,\"microsoft.authorization/roleassignmentapprovals/stages\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approvals stage\" }\r\n ,\"microsoft.authorization/roleassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment\" }\r\n ,\"microsoft.authorization/roleassignmentscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule instance\" }\r\n ,\"microsoft.authorization/roleassignmentschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule request\" }\r\n ,\"microsoft.authorization/roleassignmentschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule\" }\r\n ,\"microsoft.authorization/roledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role definition\" }\r\n ,\"microsoft.authorization/roleeligibilityscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule instance\" }\r\n ,\"microsoft.authorization/roleeligibilityschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule request\" }\r\n ,\"microsoft.authorization/roleeligibilityschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule\" }\r\n ,\"microsoft.authorization/rolemanagementalertconfigurations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert configuration\" }\r\n ,\"microsoft.authorization/rolemanagementalertdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert definition\" }\r\n ,\"microsoft.authorization/rolemanagementalertoperations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert operation\" }\r\n ,\"microsoft.authorization/rolemanagementalerts\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert\" }\r\n ,\"microsoft.authorization/rolemanagementalerts/alertincidents\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alerts alert incident\" }\r\n ,\"microsoft.authorization/rolemanagementpolicies\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy\" }\r\n ,\"microsoft.authorization/rolemanagementpolicyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy assignment\" }\r\n ,\"microsoft.automanage/bestpractices\": { \"SingularDisplayName\": \"Microsoft.Automanage best practice\" }\r\n ,\"microsoft.automanage/bestpractices/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage best practices version\" }\r\n ,\"microsoft.automanage/configurationprofileassignments\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignment\" }\r\n ,\"microsoft.automanage/configurationprofileassignments/reports\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignments report\" }\r\n ,\"microsoft.automanage/configurationprofiles\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile\" }\r\n ,\"microsoft.automanage/configurationprofiles/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profiles version\" }\r\n ,\"microsoft.automanage/serviceprincipals\": { \"SingularDisplayName\": \"ServicePrincipals\" }\r\n ,\"microsoft.automation/automationaccounts\": { \"SingularDisplayName\": \"Automation account\" }\r\n ,\"microsoft.automation/automationaccounts/hybridrunbookworkergroups\": { \"SingularDisplayName\": \"Automation hybrid worker group\" }\r\n ,\"microsoft.automation/automationaccounts/runbooks\": { \"SingularDisplayName\": \"Automation runbook\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform account\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/accounts/datapools\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform accounts data pool\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform workspace\" }\r\n ,\"microsoft.avs/privateclouds\": { \"SingularDisplayName\": \"Azure VMware Solution private cloud\" }\r\n ,\"microsoft.awsconnector/accessanalyzeranalyzers\": { \"SingularDisplayName\": \"Access Analyzer Analyzer\" }\r\n ,\"microsoft.awsconnector/acmcertificatesummaries\": { \"SingularDisplayName\": \"ACM Certificate Summary\" }\r\n ,\"microsoft.awsconnector/apigatewayrestapis\": { \"SingularDisplayName\": \"Api Gateway Rest Api\" }\r\n ,\"microsoft.awsconnector/apigatewaystages\": { \"SingularDisplayName\": \"Api Gateway Stage\" }\r\n ,\"microsoft.awsconnector/applicationautoscalingscalabletargets\": { \"SingularDisplayName\": \"Application Auto Scaling Scalable Target\" }\r\n ,\"microsoft.awsconnector/appsyncgraphqlapis\": { \"SingularDisplayName\": \"App Sync Graphql Api\" }\r\n ,\"microsoft.awsconnector/autoscalingautoscalinggroups\": { \"SingularDisplayName\": \"Auto Scaling Auto Scaling Group\" }\r\n ,\"microsoft.awsconnector/cloudformationstacks\": { \"SingularDisplayName\": \"Cloud Formation Stack\" }\r\n ,\"microsoft.awsconnector/cloudformationstacksets\": { \"SingularDisplayName\": \"Cloud Formation Stack Set\" }\r\n ,\"microsoft.awsconnector/cloudfrontdistributions\": { \"SingularDisplayName\": \"Cloud Front Distribution\" }\r\n ,\"microsoft.awsconnector/cloudtrailtrails\": { \"SingularDisplayName\": \"Cloud Trail Trail\" }\r\n ,\"microsoft.awsconnector/cloudwatchalarms\": { \"SingularDisplayName\": \"Cloud Watch Alarm\" }\r\n ,\"microsoft.awsconnector/codebuildprojects\": { \"SingularDisplayName\": \"Code Build Project\" }\r\n ,\"microsoft.awsconnector/codebuildsourcecredentialsinfos\": { \"SingularDisplayName\": \"Code Build Source Credentials Info\" }\r\n ,\"microsoft.awsconnector/configserviceconfigurationrecorders\": { \"SingularDisplayName\": \"Config Service Configuration Recorder\" }\r\n ,\"microsoft.awsconnector/configserviceconfigurationrecorderstatuses\": { \"SingularDisplayName\": \"Config Service Configuration Recorder Status\" }\r\n ,\"microsoft.awsconnector/configservicedeliverychannels\": { \"SingularDisplayName\": \"Config Service Delivery Channel\" }\r\n ,\"microsoft.awsconnector/databasemigrationservicereplicationinstances\": { \"SingularDisplayName\": \"Database Migration Service Replication Instance\" }\r\n ,\"microsoft.awsconnector/daxclusters\": { \"SingularDisplayName\": \"DAX Cluster\" }\r\n ,\"microsoft.awsconnector/dynamodbcontinuousbackupsdescriptions\": { \"SingularDisplayName\": \"Dynamo DB Continuous Backups Description\" }\r\n ,\"microsoft.awsconnector/dynamodbtables\": { \"SingularDisplayName\": \"Dynamo DB Table\" }\r\n ,\"microsoft.awsconnector/ec2accountattributes\": { \"SingularDisplayName\": \"EC2 Account Attribute\" }\r\n ,\"microsoft.awsconnector/ec2addresses\": { \"SingularDisplayName\": \"EC2 Address\" }\r\n ,\"microsoft.awsconnector/ec2flowlogs\": { \"SingularDisplayName\": \"EC2 Flow Log\" }\r\n ,\"microsoft.awsconnector/ec2images\": { \"SingularDisplayName\": \"EC2 Image\" }\r\n ,\"microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\r\n ,\"microsoft.awsconnector/ec2instancestatuses\": { \"SingularDisplayName\": \"EC2 Instance Status\" }\r\n ,\"microsoft.awsconnector/ec2ipams\": { \"SingularDisplayName\": \"EC2 Ipam\" }\r\n ,\"microsoft.awsconnector/ec2keypairs\": { \"SingularDisplayName\": \"EC2 Key Pair\" }\r\n ,\"microsoft.awsconnector/ec2networkacls\": { \"SingularDisplayName\": \"EC2 Network Acl\" }\r\n ,\"microsoft.awsconnector/ec2networkinterfaces\": { \"SingularDisplayName\": \"EC2 Network Interface\" }\r\n ,\"microsoft.awsconnector/ec2routetables\": { \"SingularDisplayName\": \"EC2 Route Table\" }\r\n ,\"microsoft.awsconnector/ec2securitygroups\": { \"SingularDisplayName\": \"EC2 Security Group\" }\r\n ,\"microsoft.awsconnector/ec2snapshots\": { \"SingularDisplayName\": \"EC2 Snapshot\" }\r\n ,\"microsoft.awsconnector/ec2subnets\": { \"SingularDisplayName\": \"EC2 Subnet\" }\r\n ,\"microsoft.awsconnector/ec2volumes\": { \"SingularDisplayName\": \"EC2 Volume\" }\r\n ,\"microsoft.awsconnector/ec2vpcendpoints\": { \"SingularDisplayName\": \"EC2 VPCEndpoint\" }\r\n ,\"microsoft.awsconnector/ec2vpcpeeringconnections\": { \"SingularDisplayName\": \"EC2 VPCPeering Connection\" }\r\n ,\"microsoft.awsconnector/ec2vpcs\": { \"SingularDisplayName\": \"EC2 VPC\" }\r\n ,\"microsoft.awsconnector/ecrimagedetails\": { \"SingularDisplayName\": \"ECR Image Detail\" }\r\n ,\"microsoft.awsconnector/ecrrepositories\": { \"SingularDisplayName\": \"ECR Repository\" }\r\n ,\"microsoft.awsconnector/ecsclusters\": { \"SingularDisplayName\": \"ECS Cluster\" }\r\n ,\"microsoft.awsconnector/ecsservices\": { \"SingularDisplayName\": \"ECS Service\" }\r\n ,\"microsoft.awsconnector/ecstaskdefinitions\": { \"SingularDisplayName\": \"ECS Task Definition\" }\r\n ,\"microsoft.awsconnector/efsfilesystems\": { \"SingularDisplayName\": \"EFS File System\" }\r\n ,\"microsoft.awsconnector/efsmounttargets\": { \"SingularDisplayName\": \"EFS Mount Target\" }\r\n ,\"microsoft.awsconnector/eksnodegroups\": { \"SingularDisplayName\": \"EKS Nodegroup\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkapplications\": { \"SingularDisplayName\": \"Elastic Beanstalk Application\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkconfigurationtemplates\": { \"SingularDisplayName\": \"Elastic Beanstalk Configuration Template\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkenvironments\": { \"SingularDisplayName\": \"Elastic Beanstalk Environment\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2listeners\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Listener\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2loadbalancers\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Load Balancer\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2targetgroups\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Target Group\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2targethealthdescriptions\": { \"SingularDisplayName\": \"Elastic Load Balancing v2 Target Health Description\" }\r\n ,\"microsoft.awsconnector/elasticsearchdomains\": { \"SingularDisplayName\": \"Elasticsearch Domain\" }\r\n ,\"microsoft.awsconnector/emrclusters\": { \"SingularDisplayName\": \"EMR Cluster\" }\r\n ,\"microsoft.awsconnector/guarddutydetectors\": { \"SingularDisplayName\": \"Guard Duty Detector\" }\r\n ,\"microsoft.awsconnector/iamaccesskeylastuseds\": { \"SingularDisplayName\": \"IAM Access Key Last Used\" }\r\n ,\"microsoft.awsconnector/iamaccesskeymetadata\": { \"SingularDisplayName\": \"IAM Access Key Metadata\" }\r\n ,\"microsoft.awsconnector/iamgroups\": { \"SingularDisplayName\": \"IAM Group\" }\r\n ,\"microsoft.awsconnector/iaminstanceprofiles\": { \"SingularDisplayName\": \"IAM Instance Profile\" }\r\n ,\"microsoft.awsconnector/iammanagedpolicies\": { \"SingularDisplayName\": \"IAM Managed Policy\" }\r\n ,\"microsoft.awsconnector/iammfadevices\": { \"SingularDisplayName\": \"IAM MFADevice\" }\r\n ,\"microsoft.awsconnector/iampasswordpolicies\": { \"SingularDisplayName\": \"IAM Password Policy\" }\r\n ,\"microsoft.awsconnector/iampolicyversions\": { \"SingularDisplayName\": \"IAM Policy Version\" }\r\n ,\"microsoft.awsconnector/iamroles\": { \"SingularDisplayName\": \"IAM Role\" }\r\n ,\"microsoft.awsconnector/iamservercertificates\": { \"SingularDisplayName\": \"IAM Server Certificate\" }\r\n ,\"microsoft.awsconnector/iamuserpolicies\": { \"SingularDisplayName\": \"IAM User Policy\" }\r\n ,\"microsoft.awsconnector/iamvirtualmfadevices\": { \"SingularDisplayName\": \"IAM Virtual MFADevice\" }\r\n ,\"microsoft.awsconnector/kmsaliases\": { \"SingularDisplayName\": \"KMS Alias\" }\r\n ,\"microsoft.awsconnector/kmskeys\": { \"SingularDisplayName\": \"KMS Key\" }\r\n ,\"microsoft.awsconnector/lambdafunctioncodelocations\": { \"SingularDisplayName\": \"Lambda Function Code Location\" }\r\n ,\"microsoft.awsconnector/lambdafunctionconfigurations\": { \"SingularDisplayName\": \"Microsoft.AwsConnector lambda function configuration\" }\r\n ,\"microsoft.awsconnector/lambdafunctions\": { \"SingularDisplayName\": \"Lambda Function\" }\r\n ,\"microsoft.awsconnector/licensemanagerlicenses\": { \"SingularDisplayName\": \"License Manager License\" }\r\n ,\"microsoft.awsconnector/lightsailbuckets\": { \"SingularDisplayName\": \"Lightsail Bucket\" }\r\n ,\"microsoft.awsconnector/lightsailinstances\": { \"SingularDisplayName\": \"Lightsail Instance\" }\r\n ,\"microsoft.awsconnector/logsloggroups\": { \"SingularDisplayName\": \"Logs Log Group\" }\r\n ,\"microsoft.awsconnector/logslogstreams\": { \"SingularDisplayName\": \"Logs Log Stream\" }\r\n ,\"microsoft.awsconnector/logsmetricfilters\": { \"SingularDisplayName\": \"Logs Metric Filter\" }\r\n ,\"microsoft.awsconnector/logssubscriptionfilters\": { \"SingularDisplayName\": \"Logs Subscription Filter\" }\r\n ,\"microsoft.awsconnector/macie2jobsummaries\": { \"SingularDisplayName\": \"Macie2 Job Summary\" }\r\n ,\"microsoft.awsconnector/macieallowlists\": { \"SingularDisplayName\": \"Macie Allow List\" }\r\n ,\"microsoft.awsconnector/networkfirewallfirewallpolicies\": { \"SingularDisplayName\": \"Network Firewall Firewall Policy\" }\r\n ,\"microsoft.awsconnector/networkfirewallfirewalls\": { \"SingularDisplayName\": \"Network Firewall Firewall\" }\r\n ,\"microsoft.awsconnector/networkfirewallrulegroups\": { \"SingularDisplayName\": \"Network Firewall Rule Group\" }\r\n ,\"microsoft.awsconnector/opensearchdomainstatuses\": { \"SingularDisplayName\": \"Open Search Domain Status\" }\r\n ,\"microsoft.awsconnector/opensearchservicedomains\": { \"SingularDisplayName\": \"Open Search Service Domain\" }\r\n ,\"microsoft.awsconnector/organizationsaccounts\": { \"SingularDisplayName\": \"Organizations Account\" }\r\n ,\"microsoft.awsconnector/organizationsorganizations\": { \"SingularDisplayName\": \"Organizations Organization\" }\r\n ,\"microsoft.awsconnector/rdsdbclusters\": { \"SingularDisplayName\": \"RDS DBCluster\" }\r\n ,\"microsoft.awsconnector/rdsdbinstances\": { \"SingularDisplayName\": \"RDS DBInstance\" }\r\n ,\"microsoft.awsconnector/rdsdbsnapshotattributesresults\": { \"SingularDisplayName\": \"RDS DBSnapshot Attributes Result\" }\r\n ,\"microsoft.awsconnector/rdsdbsnapshots\": { \"SingularDisplayName\": \"RDS DBSnapshot\" }\r\n ,\"microsoft.awsconnector/rdseventsubscriptions\": { \"SingularDisplayName\": \"RDS Event Subscription\" }\r\n ,\"microsoft.awsconnector/rdsexporttasks\": { \"SingularDisplayName\": \"RDS Export Task\" }\r\n ,\"microsoft.awsconnector/redshiftclusterparametergroups\": { \"SingularDisplayName\": \"Redshift Cluster Parameter Group\" }\r\n ,\"microsoft.awsconnector/redshiftclusters\": { \"SingularDisplayName\": \"Redshift Cluster\" }\r\n ,\"microsoft.awsconnector/route53domainsdomainsummaries\": { \"SingularDisplayName\": \"Route 53 Domains Domain Summary\" }\r\n ,\"microsoft.awsconnector/route53hostedzones\": { \"SingularDisplayName\": \"Route53 Hosted Zone\" }\r\n ,\"microsoft.awsconnector/route53resourcerecordsets\": { \"SingularDisplayName\": \"Route 53 Resource Record Set\" }\r\n ,\"microsoft.awsconnector/s3accesscontrolpolicies\": { \"SingularDisplayName\": \"S3 Access Control Policy\" }\r\n ,\"microsoft.awsconnector/s3accesspoints\": { \"SingularDisplayName\": \"S3 Access Point\" }\r\n ,\"microsoft.awsconnector/s3bucketpolicies\": { \"SingularDisplayName\": \"S3 Bucket Policy\" }\r\n ,\"microsoft.awsconnector/s3buckets\": { \"SingularDisplayName\": \"S3 Bucket\" }\r\n ,\"microsoft.awsconnector/s3controlmultiregionaccesspointpolicydocuments\": { \"SingularDisplayName\": \"S3 Control Multi Region Access Point Policy Document\" }\r\n ,\"microsoft.awsconnector/sagemakerapps\": { \"SingularDisplayName\": \"Sage Maker App\" }\r\n ,\"microsoft.awsconnector/sagemakerdevices\": { \"SingularDisplayName\": \"Sage Maker Device\" }\r\n ,\"microsoft.awsconnector/sagemakerimages\": { \"SingularDisplayName\": \"Sage Maker Image\" }\r\n ,\"microsoft.awsconnector/sagemakernotebookinstancesummaries\": { \"SingularDisplayName\": \"Sage Maker Notebook Instance Summary\" }\r\n ,\"microsoft.awsconnector/secretsmanagerresourcepolicies\": { \"SingularDisplayName\": \"Secrets Manager Resource Policy\" }\r\n ,\"microsoft.awsconnector/secretsmanagersecrets\": { \"SingularDisplayName\": \"Secrets Manager Secret\" }\r\n ,\"microsoft.awsconnector/snssubscriptions\": { \"SingularDisplayName\": \"SNS Subscription\" }\r\n ,\"microsoft.awsconnector/snstopics\": { \"SingularDisplayName\": \"SNS Topic\" }\r\n ,\"microsoft.awsconnector/sqsqueues\": { \"SingularDisplayName\": \"SQS Queue\" }\r\n ,\"microsoft.awsconnector/ssminstanceinformations\": { \"SingularDisplayName\": \"SSM Instance Information\" }\r\n ,\"microsoft.awsconnector/ssmparameters\": { \"SingularDisplayName\": \"SSM Parameter\" }\r\n ,\"microsoft.awsconnector/ssmresourcecompliancesummaryitems\": { \"SingularDisplayName\": \"SSM Resource Compliance Summary Item\" }\r\n ,\"microsoft.awsconnector/wafv2ipsets\": { \"SingularDisplayName\": \"WAFv2 IPSet\" }\r\n ,\"microsoft.awsconnector/wafv2loggingconfigurations\": { \"SingularDisplayName\": \"WAFv2 Logging Configuration\" }\r\n ,\"microsoft.awsconnector/wafv2webaclassociations\": { \"SingularDisplayName\": \"WAFv2 Web ACLAssociation\" }\r\n ,\"microsoft.awsconnector/wafwebaclsummaries\": { \"SingularDisplayName\": \"WAF Web ACLSummary\" }\r\n ,\"microsoft.azureactivedirectory/b2cdirectories\": { \"SingularDisplayName\": \"B2C tenant\" }\r\n ,\"microsoft.azureactivedirectory/ciamdirectories\": { \"SingularDisplayName\": \"External Configuration Tenant\" }\r\n ,\"microsoft.azureactivedirectory/guestusages\": { \"SingularDisplayName\": \"Guest Usage\" }\r\n ,\"microsoft.azurearcdata/datacontrollers\": { \"SingularDisplayName\": \"Azure Arc data controller\" }\r\n ,\"microsoft.azurearcdata/mysqlserver\": { \"SingularDisplayName\": \"MySql Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/postgresinstances\": { \"SingularDisplayName\": \"PostgreSQL server ? Azure Arc\" }\r\n ,\"microsoft.azurearcdata/postgressqlserver\": { \"SingularDisplayName\": \"PostgresSql Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlmanagedinstances\": { \"SingularDisplayName\": \"SQL managed instance - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserveresulicenses\": { \"SingularDisplayName\": \"SQL Server ESU license\" }\r\n ,\"microsoft.azurearcdata/sqlserverinstances\": { \"SingularDisplayName\": \"SQL Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserverinstances/databases\": { \"SingularDisplayName\": \"SQL Server database - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserverlicenses\": { \"SingularDisplayName\": \"SQL Server License\" }\r\n ,\"microsoft.azurebusinesscontinuity/deletedunifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity deleted unified protected item\" }\r\n ,\"microsoft.azurebusinesscontinuity/unifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity unified protected item\" }\r\n ,\"microsoft.azurecis/aadapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis AAD application\" }\r\n ,\"microsoft.azurecis/addressrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis address record\" }\r\n ,\"microsoft.azurecis/autopilotenvironments\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot environment\" }\r\n ,\"microsoft.azurecis/autopilotmachinefunctions\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot machine function\" }\r\n ,\"microsoft.azurecis/autopilotsoftwareloadbalancevirtualips\": { \"SingularDisplayName\": \"Microsoft.AzureCis auto pilot software load balance virtual IP\" }\r\n ,\"microsoft.azurecis/azcopies\": { \"SingularDisplayName\": \"Microsoft.AzureCis az copy\" }\r\n ,\"microsoft.azurecis/canonicalnamerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis canonical name record\" }\r\n ,\"microsoft.azurecis/dsmsallowlists\": { \"SingularDisplayName\": \"Microsoft.AzureCis ds msallowlist\" }\r\n ,\"microsoft.azurecis/dsmscertificates\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms certificate\" }\r\n ,\"microsoft.azurecis/dsmsrootfolders\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms root folder\" }\r\n ,\"microsoft.azurecis/dstsapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts application\" }\r\n ,\"microsoft.azurecis/dstsserviceaccounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service account\" }\r\n ,\"microsoft.azurecis/dstsserviceclientidentities\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service client identity\" }\r\n ,\"microsoft.azurecis/genericgenevaactions\": { \"SingularDisplayName\": \"Microsoft.AzureCis generic geneva action\" }\r\n ,\"microsoft.azurecis/plannedquotas\": { \"SingularDisplayName\": \"Microsoft.AzureCis planned quota\" }\r\n ,\"microsoft.azurecis/pointerrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis pointer record\" }\r\n ,\"microsoft.azurecis/publishconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis publish config value\" }\r\n ,\"microsoft.azurecis/pushagentv2accounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis push agent v2 account\" }\r\n ,\"microsoft.azurecis/servicerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis service record\" }\r\n ,\"microsoft.azurecis/sharedconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis shared config value\" }\r\n ,\"microsoft.azurecloudmetadata/clouds\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata cloud\" }\r\n ,\"microsoft.azurecloudmetadata/clouds/geographies\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geography\" }\r\n ,\"microsoft.azurecloudmetadata/clouds/geographies/regions\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geographies region\" }\r\n ,\"microsoft.azuredatatransfer/connections\": { \"SingularDisplayName\": \"Connection\" }\r\n ,\"microsoft.azuredatatransfer/connections/flows\": { \"SingularDisplayName\": \"Flow\" }\r\n ,\"microsoft.azuredatatransfer/pipelines\": { \"SingularDisplayName\": \"Pipeline\" }\r\n ,\"microsoft.azurefleet/fleets\": { \"SingularDisplayName\": \"Compute Fleet\" }\r\n ,\"microsoft.azurefleet/fleetscomputehub\": { \"SingularDisplayName\": \"Compute Fleet\" }\r\n ,\"microsoft.azureimagetestingforlinux/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job\" }\r\n ,\"microsoft.azureimagetestingforlinux/jobtemplates\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job template\" }\r\n ,\"microsoft.azurelargeinstance/azurelargeinstances\": { \"SingularDisplayName\": \"Azure Large Instance\" }\r\n ,\"microsoft.azurelargeinstance/azurelargestorageinstances\": { \"SingularDisplayName\": \"Microsoft.AzureLargeInstance Azure large storage instance\" }\r\n ,\"microsoft.azurepercept/accounts\": { \"SingularDisplayName\": \"Microsoft.AzurePercept account\" }\r\n ,\"microsoft.azurepercept/accounts/devices\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts device\" }\r\n ,\"microsoft.azurepercept/accounts/devices/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts devices sensor\" }\r\n ,\"microsoft.azurepercept/accounts/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts sensor\" }\r\n ,\"microsoft.azurepercept/accounts/solutioninstances\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solutioninstance\" }\r\n ,\"microsoft.azurepercept/accounts/solutions\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solution\" }\r\n ,\"microsoft.azurepercept/accounts/targets\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts target\" }\r\n ,\"microsoft.azureplaywrightservice/accounts\": { \"SingularDisplayName\": \"Playwright Testing\" }\r\n ,\"microsoft.azurescan/scanningaccounts\": { \"SingularDisplayName\": \"ESRP Scan\" }\r\n ,\"microsoft.azuresphere/catalogs\": { \"SingularDisplayName\": \"Azure Sphere Catalog\" }\r\n ,\"microsoft.azurespherev2/catalogs\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalog\" }\r\n ,\"microsoft.azurespherev2/catalogs/artifacts\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs artifact\" }\r\n ,\"microsoft.azurespherev2/catalogs/certificates\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs certificate\" }\r\n ,\"microsoft.azurespherev2/catalogs/deviceregistrations\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs device registration\" }\r\n ,\"microsoft.azurespherev2/catalogs/provisioningpackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs provisioning package\" }\r\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channel\" }\r\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels/deployments\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channels deployment\" }\r\n ,\"microsoft.azurespherev2/catalogs/updatepackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs update package\" }\r\n ,\"microsoft.azurestack/cloudmanifestfiles\": { \"SingularDisplayName\": \"Microsoft.AzureStack cloud manifest file\" }\r\n ,\"microsoft.azurestack/linkedsubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack linked subscription\" }\r\n ,\"microsoft.azurestack/registrations\": { \"SingularDisplayName\": \"Microsoft.AzureStack registration\" }\r\n ,\"microsoft.azurestack/registrations/customersubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations customer subscription\" }\r\n ,\"microsoft.azurestack/registrations/products\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations product\" }\r\n ,\"microsoft.azurestackhci/clusters\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/clusters/updates/updateruns\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/clusters/updatesummaries\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/devicepools\": { \"SingularDisplayName\": \"Azure Stack\" }\r\n ,\"microsoft.azurestackhci/edgedevices\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge device\" }\r\n ,\"microsoft.azurestackhci/edgedevices/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge devices job\" }\r\n ,\"microsoft.azurestackhci/edgemachines\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machine\" }\r\n ,\"microsoft.azurestackhci/edgemachines/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machines job\" }\r\n ,\"microsoft.azurestackhci/edgenodepools\": { \"SingularDisplayName\": \"Azure Stack\" }\r\n ,\"microsoft.azurestackhci/galleryimages\": { \"SingularDisplayName\": \"Azure Local Gallery image\" }\r\n ,\"microsoft.azurestackhci/logicalnetworks\": { \"SingularDisplayName\": \"Azure Local Logical network\" }\r\n ,\"microsoft.azurestackhci/marketplacegalleryimages\": { \"SingularDisplayName\": \"Azure Local Marketplace Gallery image\" }\r\n ,\"microsoft.azurestackhci/networkinterfaces\": { \"SingularDisplayName\": \"Azure Local VM Network Interface\" }\r\n ,\"microsoft.azurestackhci/networksecuritygroups\": { \"SingularDisplayName\": \"Azure Local Network Security Group\" }\r\n ,\"microsoft.azurestackhci/networksecuritygroups/securityrules\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI network security groups security rule\" }\r\n ,\"microsoft.azurestackhci/storagecontainers\": { \"SingularDisplayName\": \"Azure Local Storage path\" }\r\n ,\"microsoft.azurestackhci/virtualharddisks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual hard disk\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instance\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances guest agent\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.azurestackhci/virtualmachines\": { \"SingularDisplayName\": \"Azure Local virtual machine - Azure Arc\" }\r\n ,\"microsoft.azurestackhci/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual network\" }\r\n ,\"microsoft.backupsolutions/vmwareapplications\": { \"SingularDisplayName\": \"Microsoft.BackupSolutions vmware application\" }\r\n ,\"microsoft.bakeryhybrid/pies\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid py\" }\r\n ,\"microsoft.bakeryhybrid/pies/nestedresourcetype\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid pies nested resource type\" }\r\n ,\"microsoft.baremetal/baremetalconnections\": { \"SingularDisplayName\": \"Microsoft.BareMetal bare metal connection\" }\r\n ,\"microsoft.baremetal/crayservers\": { \"SingularDisplayName\": \"Cray Server\" }\r\n ,\"microsoft.baremetal/monitoringservers\": { \"SingularDisplayName\": \"Monitoring Server\" }\r\n ,\"microsoft.baremetal/peeringsettings\": { \"SingularDisplayName\": \"Microsoft.BareMetal peering setting\" }\r\n ,\"microsoft.baremetalinfrastructure/baremetalinstances\": { \"SingularDisplayName\": \"BareMetal Instance\" }\r\n ,\"microsoft.baremetalinfrastructure/baremetalstorageinstances\": { \"SingularDisplayName\": \"Microsoft.BareMetalInfrastructure bare metal storage instance\" }\r\n ,\"microsoft.batch/batchaccounts\": { \"SingularDisplayName\": \"Batch account\" }\r\n ,\"microsoft.billing/billingaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing account\" }\r\n ,\"microsoft.billing/billingaccounts/agreements\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts agreement\" }\r\n ,\"microsoft.billing/billingaccounts/associatedtenants\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts associated tenant\" }\r\n ,\"microsoft.billing/billingaccounts/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts available balance\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profile\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles available balance\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers transfer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/instructions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles instruction\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice section\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections product\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections transfer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/paymentmethodlinks\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles payment method link\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles policy\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/transactions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles transaction\" }\r\n ,\"microsoft.billing/billingaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptionaliases\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription aliase\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptions/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscriptions invoice\" }\r\n ,\"microsoft.billing/billingaccounts/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customer\" }\r\n ,\"microsoft.billing/billingaccounts/customers/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/customers/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers policy\" }\r\n ,\"microsoft.billing/billingaccounts/customers/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers product\" }\r\n ,\"microsoft.billing/billingaccounts/departments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts department\" }\r\n ,\"microsoft.billing/billingaccounts/departments/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/departments/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/departments/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments enrollment account\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment account\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\r\n ,\"microsoft.billing/billingaccounts/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\r\n ,\"microsoft.billing/billingaccounts/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice section\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections product\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections transfer\" }\r\n ,\"microsoft.billing/billingaccounts/lineofcredit\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts line of credit\" }\r\n ,\"microsoft.billing/billingaccounts/migrations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts migration\" }\r\n ,\"microsoft.billing/billingaccounts/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts payment method\" }\r\n ,\"microsoft.billing/billingaccounts/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts policy\" }\r\n ,\"microsoft.billing/billingaccounts/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts product\" }\r\n ,\"microsoft.billing/billingaccounts/reservationorders\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation order\" }\r\n ,\"microsoft.billing/billingaccounts/reservationorders/reservations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation orders reservation\" }\r\n ,\"microsoft.billing/billingaccounts/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\r\n ,\"microsoft.billing/billingaccounts/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\r\n ,\"microsoft.billing/billingperiods\": { \"SingularDisplayName\": \"Microsoft.Billing billing period\" }\r\n ,\"microsoft.billing/billingproperty\": { \"SingularDisplayName\": \"Microsoft.Billing billing property\" }\r\n ,\"microsoft.billing/billingrequests\": { \"SingularDisplayName\": \"Microsoft.Billing billing request\" }\r\n ,\"microsoft.billing/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing role assignment\" }\r\n ,\"microsoft.billing/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing role definition\" }\r\n ,\"microsoft.billing/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing enrollment account\" }\r\n ,\"microsoft.billing/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing payment method\" }\r\n ,\"microsoft.billing/policies\": { \"SingularDisplayName\": \"Microsoft.Billing policy\" }\r\n ,\"microsoft.billing/promotions\": { \"SingularDisplayName\": \"Microsoft.Billing promotion\" }\r\n ,\"microsoft.billing/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing transfer\" }\r\n ,\"microsoft.billingbenefits/credits\": { \"SingularDisplayName\": \"Credit\" }\r\n ,\"microsoft.billingbenefits/discounts\": { \"SingularDisplayName\": \"Discount\" }\r\n ,\"microsoft.billingbenefits/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\r\n ,\"microsoft.billingbenefits/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\r\n ,\"microsoft.billingbenefits/maccs\": { \"SingularDisplayName\": \"Microsoft Azure Consumption Commitment\" }\r\n ,\"microsoft.billingbenefits/reservationorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits reservation order aliase\" }\r\n ,\"microsoft.billingbenefits/savingsplanorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits savings plan order aliase\" }\r\n ,\"microsoft.billingbenefits/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\r\n ,\"microsoft.billingbenefits/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\r\n ,\"microsoft.bing/accounts\": { \"SingularDisplayName\": \"Bing Resource\" }\r\n ,\"microsoft.blockchain/blockchainmembers\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain member\" }\r\n ,\"microsoft.blockchain/blockchainmembers/transactionnodes\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain members transaction node\" }\r\n ,\"microsoft.blockchaintokens/tokenservices\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token service\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/blockchainnetworks\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services blockchain network\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/groups\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services group\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/groups/accounts\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services groups account\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/tokentemplates\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services token template\" }\r\n ,\"microsoft.bluefin/instances\": { \"SingularDisplayName\": \"Microsoft.Bluefin instance\" }\r\n ,\"microsoft.bluefin/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances dataset\" }\r\n ,\"microsoft.bluefin/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances pipeline\" }\r\n ,\"microsoft.blueprint/blueprintassignments\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint assignment\" }\r\n ,\"microsoft.blueprint/blueprints\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint\" }\r\n ,\"microsoft.blueprint/blueprints/artifacts\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints artifact\" }\r\n ,\"microsoft.blueprint/blueprints/versions\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints version\" }\r\n ,\"microsoft.botservice/botservices\": { \"SingularDisplayName\": \"Bot Service\" }\r\n ,\"microsoft.cache/redis\": { \"SingularDisplayName\": \"Redis cache\" }\r\n ,\"microsoft.cache/redisenterprise\": { \"SingularDisplayName\": \"Azure Managed Redis\" }\r\n ,\"microsoft.cache/redisenterprise/databases\": { \"SingularDisplayName\": \"Redis Enterprise database\" }\r\n ,\"microsoft.capacity/reservationorders\": { \"SingularDisplayName\": \"Reservation order\" }\r\n ,\"microsoft.capacity/reservationorders/reservations\": { \"SingularDisplayName\": \"Reservation\" }\r\n ,\"microsoft.cascade/sites\": { \"SingularDisplayName\": \"Microsoft.Cascade site\" }\r\n ,\"microsoft.cdn/cdnwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Content Delivery Network WAF policy\" }\r\n ,\"microsoft.cdn/edgeactions\": { \"SingularDisplayName\": \"Edge Action\" }\r\n ,\"microsoft.cdn/profiles\": { \"SingularDisplayName\": \"Front Door and CDN profile\" }\r\n ,\"microsoft.cdn/profiles/afdendpoints\": { \"SingularDisplayName\": \"Endpoint\" }\r\n ,\"microsoft.cdn/profiles/afdendpoints/routes\": { \"SingularDisplayName\": \"Route\" }\r\n ,\"microsoft.cdn/profiles/customdomains\": { \"SingularDisplayName\": \"Custom domain\" }\r\n ,\"microsoft.cdn/profiles/endpoints\": { \"SingularDisplayName\": \"CDN endpoint\" }\r\n ,\"microsoft.cdn/profiles/endpoints/customdomains\": { \"SingularDisplayName\": \"CDN custom domain\" }\r\n ,\"microsoft.cdn/profiles/endpoints/origins\": { \"SingularDisplayName\": \"CDN origin\" }\r\n ,\"microsoft.cdn/profiles/origingroups\": { \"SingularDisplayName\": \"Origin group\" }\r\n ,\"microsoft.cdn/profiles/origingroups/origins\": { \"SingularDisplayName\": \"Origin\" }\r\n ,\"microsoft.cdn/profiles/rulesets\": { \"SingularDisplayName\": \"Rule set\" }\r\n ,\"microsoft.cdn/profiles/rulesets/rules\": { \"SingularDisplayName\": \"Rule\" }\r\n ,\"microsoft.cdn/profiles/secrets\": { \"SingularDisplayName\": \"Secret\" }\r\n ,\"microsoft.cdn/profiles/securitypolicies\": { \"SingularDisplayName\": \"Security policy\" }\r\n ,\"microsoft.certificateregistration/certificateorders\": { \"SingularDisplayName\": \"App Service certificate\" }\r\n ,\"microsoft.certify/testsuites\": { \"SingularDisplayName\": \"Microsoft.Certify test suite\" }\r\n ,\"microsoft.certify/validationjobs\": { \"SingularDisplayName\": \"Microsoft.Certify validation job\" }\r\n ,\"microsoft.changeanalysis/profile\": { \"SingularDisplayName\": \"Microsoft.ChangeAnalysis profile\" }\r\n ,\"microsoft.changesafety/changestates\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change state\" }\r\n ,\"microsoft.changesafety/changestates/stageprogressions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change states stage progression\" }\r\n ,\"microsoft.changesafety/stagemaps\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety stage map\" }\r\n ,\"microsoft.changesafety/validations\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validation\" }\r\n ,\"microsoft.changesafety/validators\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validator\" }\r\n ,\"microsoft.changesafety/validators/versions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validators version\" }\r\n ,\"microsoft.chaos/experiments\": { \"SingularDisplayName\": \"Chaos Experiment\" }\r\n ,\"microsoft.chaos/privateaccesses\": { \"SingularDisplayName\": \"Agent Private Access\" }\r\n ,\"microsoft.chaos/targets\": { \"SingularDisplayName\": \"Microsoft.Chaos target\" }\r\n ,\"microsoft.chaos/targets/capabilities\": { \"SingularDisplayName\": \"Microsoft.Chaos targets capability\" }\r\n ,\"microsoft.classiccompute/domainnames\": { \"SingularDisplayName\": \"Cloud service (classic)\" }\r\n ,\"microsoft.classiccompute/domainnames/slots/roles\": { \"SingularDisplayName\": \"Cloud service role (classic)\" }\r\n ,\"microsoft.classiccompute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine (classic)\" }\r\n ,\"microsoft.classicnetwork/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group (classic)\" }\r\n ,\"microsoft.classicnetwork/reservedips\": { \"SingularDisplayName\": \"Reserved IP address (classic)\" }\r\n ,\"microsoft.classicnetwork/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network (classic)\" }\r\n })[tolower(id)]\r\n}\r\n", - "$fxv#1": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_2(id: string) {\r\n dynamic({\r\n \"microsoft.classicstorage/storageaccounts\": { \"SingularDisplayName\": \"Storage account (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/disks\": { \"SingularDisplayName\": \"Disk (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/osimages\": { \"SingularDisplayName\": \"OS image (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/vmimages\": { \"SingularDisplayName\": \"VM image (classic)\" }\r\n ,\"microsoft.cleanroom/cleanrooms\": { \"SingularDisplayName\": \"Microsoft.CleanRoom cleanroom\" }\r\n ,\"microsoft.cleanroom/collaborations\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaboration\" }\r\n ,\"microsoft.cleanroom/collaborations/contracts\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaborations contract\" }\r\n ,\"microsoft.cleanroom/consortiums\": { \"SingularDisplayName\": \"Microsoft.CleanRoom consortium\" }\r\n ,\"microsoft.cleanroom/microservices\": { \"SingularDisplayName\": \"Microsoft.CleanRoom microservice\" }\r\n ,\"microsoft.cloud/hubs\": { \"SingularDisplayName\": \"FinOps hub\" }\r\n ,\"microsoft.clouddeviceplatform/delegatedidentities\": { \"SingularDisplayName\": \"Microsoft.CloudDevicePlatform delegated identity\" }\r\n ,\"microsoft.cloudhealth/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\r\n ,\"microsoft.cloudtest/accounts\": { \"SingularDisplayName\": \"CloudTest Account\" }\r\n ,\"microsoft.cloudtest/buildcaches\": { \"SingularDisplayName\": \"1ES Build Cache\" }\r\n ,\"microsoft.cloudtest/hostedpools\": { \"SingularDisplayName\": \"1ES Hosted Pool\" }\r\n ,\"microsoft.cloudtest/images\": { \"SingularDisplayName\": \"1ES Image\" }\r\n ,\"microsoft.cloudtest/pools\": { \"SingularDisplayName\": \"CloudTest Pool\" }\r\n ,\"microsoft.clusterstor/nodes\": { \"SingularDisplayName\": \"ClusterStor\" }\r\n ,\"microsoft.codesigning/codesigningaccounts\": { \"SingularDisplayName\": \"Trusted Signing Account\" }\r\n ,\"microsoft.codespaces/plans\": { \"SingularDisplayName\": \"Microsoft.Codespaces plan\" }\r\n ,\"microsoft.cognitiveservices/accounts\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.cognitiveservices/accounts/projects\": { \"SingularDisplayName\": \"Azure AI Foundry project\" }\r\n ,\"microsoft.cognitiveservices/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plan\" }\r\n ,\"microsoft.cognitiveservices/commitmentplans/accountassociations\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plans account association\" }\r\n ,\"microsoft.communication/communicationservices\": { \"SingularDisplayName\": \"Communication Service\" }\r\n ,\"microsoft.communication/emailservices\": { \"SingularDisplayName\": \"Email Communication Service\" }\r\n ,\"microsoft.communication/emailservices/domains\": { \"SingularDisplayName\": \"Email Communication Services Domain\" }\r\n ,\"microsoft.community/communitytrainings\": { \"SingularDisplayName\": \"Community Training\" }\r\n ,\"microsoft.compositesolutions/compositesolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution definition\" }\r\n ,\"microsoft.compositesolutions/compositesolutions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution\" }\r\n ,\"microsoft.compute/availabilitysets\": { \"SingularDisplayName\": \"Availability set\" }\r\n ,\"microsoft.compute/capacityreservationgroups\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\r\n ,\"microsoft.compute/capacityreservationgroups/capacityreservations\": { \"SingularDisplayName\": \"Capacity reservation\" }\r\n ,\"microsoft.compute/capacityreservationgroupscomputehub\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\r\n ,\"microsoft.compute/cloudservices\": { \"SingularDisplayName\": \"Cloud service (extended support)\" }\r\n ,\"microsoft.compute/computefleetinstances\": { \"SingularDisplayName\": \"Instance\" }\r\n ,\"microsoft.compute/computefleetscalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.compute/diskaccesses\": { \"SingularDisplayName\": \"Disk Access\" }\r\n ,\"microsoft.compute/diskencryptionsets\": { \"SingularDisplayName\": \"Disk Encryption Set\" }\r\n ,\"microsoft.compute/disks\": { \"SingularDisplayName\": \"Disk\" }\r\n ,\"microsoft.compute/galleries\": { \"SingularDisplayName\": \"Azure compute gallery\" }\r\n ,\"microsoft.compute/galleries/applications\": { \"SingularDisplayName\": \"VM application definition\" }\r\n ,\"microsoft.compute/galleries/applications/versions\": { \"SingularDisplayName\": \"VM application version\" }\r\n ,\"microsoft.compute/galleries/images\": { \"SingularDisplayName\": \"VM image definition\" }\r\n ,\"microsoft.compute/galleries/images/versions\": { \"SingularDisplayName\": \"VM image version\" }\r\n ,\"microsoft.compute/galleries/imagescomputehub\": { \"SingularDisplayName\": \"VM image definition\" }\r\n ,\"microsoft.compute/hostgroups\": { \"SingularDisplayName\": \"Host group\" }\r\n ,\"microsoft.compute/hostgroups/hosts\": { \"SingularDisplayName\": \"Host\" }\r\n ,\"microsoft.compute/hostgroupscomputehub\": { \"SingularDisplayName\": \"Host group\" }\r\n ,\"microsoft.compute/images\": { \"SingularDisplayName\": \"Image\" }\r\n ,\"microsoft.compute/imagescomputehub\": { \"SingularDisplayName\": \"Image\" }\r\n ,\"microsoft.compute/locations/communitygalleries/images\": { \"SingularDisplayName\": \"Community image\" }\r\n ,\"microsoft.compute/locations/communitygalleries/imagescomputehub\": { \"SingularDisplayName\": \"Community image\" }\r\n ,\"microsoft.compute/proximityplacementgroups\": { \"SingularDisplayName\": \"Proximity placement group\" }\r\n ,\"microsoft.compute/proximityplacementgroupscomputehub\": { \"SingularDisplayName\": \"Proximity placement group\" }\r\n ,\"microsoft.compute/restorepointcollections\": { \"SingularDisplayName\": \"Restore Point Collection\" }\r\n ,\"microsoft.compute/restorepointcollections/restorepoints\": { \"SingularDisplayName\": \"Restore Point\" }\r\n ,\"microsoft.compute/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\r\n ,\"microsoft.compute/sshpublickeys\": { \"SingularDisplayName\": \"SSH key\" }\r\n ,\"microsoft.compute/standbypoolinstance\": { \"SingularDisplayName\": \"Standby pool\" }\r\n ,\"microsoft.compute/virtualmachinecomputehub\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.compute/virtualmachineflexinstances\": { \"SingularDisplayName\": \"Instance\" }\r\n ,\"microsoft.compute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.compute/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.compute/virtualmachinescalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine scale set instance\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines/networkinterfaces/ipconfigurations/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\r\n ,\"microsoft.compute/virtualmachinescalesetscomputehub\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.computehub/advisorcost\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisoroperationalexcellence\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorperformance\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorreliability\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorsecurity\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/all\": { \"SingularDisplayName\": \"All resources\" }\r\n ,\"microsoft.computehub/backup\": { \"SingularDisplayName\": \"Backup job\" }\r\n ,\"microsoft.computehub/computehubmain\": { \"SingularDisplayName\": \"Compute infrastructure\" }\r\n ,\"microsoft.computehub/healthevents\": { \"SingularDisplayName\": \"Health events\" }\r\n ,\"microsoft.computehub/linuxostype\": { \"SingularDisplayName\": \"Linux OS\" }\r\n ,\"microsoft.computehub/microsoftdefenderfreetrialsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\r\n ,\"microsoft.computehub/microsoftdefenderstandardsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\r\n ,\"microsoft.computehub/outages\": { \"SingularDisplayName\": \"Outages\" }\r\n ,\"microsoft.computehub/powerstatedeallocated\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/powerstaterunning\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/powerstatestopped\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/provisioningstatefailedresources\": { \"SingularDisplayName\": \"Provisioning states\" }\r\n ,\"microsoft.computehub/provisioningstatesucceededresources\": { \"SingularDisplayName\": \"Provisioning states\" }\r\n ,\"microsoft.computehub/windowsostype\": { \"SingularDisplayName\": \"Windows OS\" }\r\n ,\"microsoft.computeschedule/autoactions\": { \"SingularDisplayName\": \"Automatic Action\" }\r\n ,\"microsoft.computeschedule/autoactions/occurrences\": { \"SingularDisplayName\": \"Microsoft.ComputeSchedule auto actions occurrence\" }\r\n ,\"microsoft.confidentialledger/ledgers\": { \"SingularDisplayName\": \"Confidential Ledger\" }\r\n ,\"microsoft.confidentialledger/managedccfs\": { \"SingularDisplayName\": \"Managed CCF App\" }\r\n ,\"microsoft.confluent/agreements\": { \"SingularDisplayName\": \"Microsoft.Confluent agreement\" }\r\n ,\"microsoft.confluent/organizations\": { \"SingularDisplayName\": \"Confluent organization\" }\r\n ,\"microsoft.connectedcache/cachenodes\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\r\n ,\"microsoft.connectedcache/enterprisecustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\r\n ,\"microsoft.connectedcache/enterprisemcccustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\r\n ,\"microsoft.connectedcache/enterprisemcccustomers/enterprisemcccachenodes\": { \"SingularDisplayName\": \"MCC CacheNode for Enterprise\" }\r\n ,\"microsoft.connectedcache/ispcustomers\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\r\n ,\"microsoft.connectedcredentials/credentials\": { \"SingularDisplayName\": \"Microsoft.ConnectedCredentials credential\" }\r\n ,\"microsoft.connectedvehicle/platformaccounts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVehicle platform account\" }\r\n ,\"microsoft.connectedvmwarevsphere/clusters\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere cluster\" }\r\n ,\"microsoft.connectedvmwarevsphere/datastores\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere datastore\" }\r\n ,\"microsoft.connectedvmwarevsphere/hosts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere host\" }\r\n ,\"microsoft.connectedvmwarevsphere/resourcepools\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere resource pool\" }\r\n ,\"microsoft.connectedvmwarevsphere/vcenters\": { \"SingularDisplayName\": \"VMware vCenter\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instance\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances guest agent\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachines\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine template\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual network\" }\r\n ,\"microsoft.consumption/budgets\": { \"SingularDisplayName\": \"Microsoft.Consumption budget\" }\r\n ,\"microsoft.consumption/credits\": { \"SingularDisplayName\": \"Microsoft.Consumption credit\" }\r\n ,\"microsoft.consumption/pricesheets\": { \"SingularDisplayName\": \"Microsoft.Consumption pricesheet\" }\r\n ,\"microsoft.containerinstance/containergroupprofiles\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profile\" }\r\n ,\"microsoft.containerinstance/containergroupprofiles/revisions\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profiles revision\" }\r\n ,\"microsoft.containerinstance/containergroups\": { \"SingularDisplayName\": \"Container instances\" }\r\n ,\"microsoft.containerinstance/ngroups\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance ngroup\" }\r\n ,\"microsoft.containerregistry/registries\": { \"SingularDisplayName\": \"Container registry\" }\r\n ,\"microsoft.containerregistry/registries/replications\": { \"SingularDisplayName\": \"Container registry replication\" }\r\n ,\"microsoft.containerregistry/registries/scopemaps\": { \"SingularDisplayName\": \"Container registry scope map\" }\r\n ,\"microsoft.containerregistry/registries/tokens\": { \"SingularDisplayName\": \"Container registry token\" }\r\n ,\"microsoft.containerregistry/registries/webhooks\": { \"SingularDisplayName\": \"Container registry webhook\" }\r\n ,\"microsoft.containerservice/fleets\": { \"SingularDisplayName\": \"Kubernetes fleet manager\" }\r\n ,\"microsoft.containerservice/managedclusters\": { \"SingularDisplayName\": \"Kubernetes service\" }\r\n ,\"microsoft.containerservice/managedclusters/managednamespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes namespace\" }\r\n ,\"microsoft.containerservice/managedclusters/namespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\r\n ,\"microsoft.containerservice/managedclustersnapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService managedclustersnapshot\" }\r\n ,\"microsoft.containerservice/snapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService snapshot\" }\r\n ,\"microsoft.containerstorage/pools\": { \"SingularDisplayName\": \"Container storage\" }\r\n ,\"microsoft.costmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.CostManagement alert\" }\r\n ,\"microsoft.costmanagement/budgets\": { \"SingularDisplayName\": \"Microsoft.CostManagement budget\" }\r\n ,\"microsoft.costmanagement/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement cloud connector\" }\r\n ,\"microsoft.costmanagement/connectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement connector\" }\r\n ,\"microsoft.costmanagement/costallocationrules\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost allocation rule\" }\r\n ,\"microsoft.costmanagement/costdetailsoperationresults\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost details operation result\" }\r\n ,\"microsoft.costmanagement/exports\": { \"SingularDisplayName\": \"Microsoft.CostManagement export\" }\r\n ,\"microsoft.costmanagement/externalbillingaccounts\": { \"SingularDisplayName\": \"Microsoft.CostManagement external billing account\" }\r\n ,\"microsoft.costmanagement/externalsubscriptions\": { \"SingularDisplayName\": \"Microsoft.CostManagement external subscription\" }\r\n ,\"microsoft.costmanagement/markuprules\": { \"SingularDisplayName\": \"Microsoft.CostManagement markup rule\" }\r\n ,\"microsoft.costmanagement/operationstatus\": { \"SingularDisplayName\": \"Microsoft.CostManagement operation statu\" }\r\n ,\"microsoft.costmanagement/reportconfigs\": { \"SingularDisplayName\": \"Microsoft.CostManagement reportconfig\" }\r\n ,\"microsoft.costmanagement/reports\": { \"SingularDisplayName\": \"Microsoft.CostManagement report\" }\r\n ,\"microsoft.costmanagement/scheduledactions\": { \"SingularDisplayName\": \"Microsoft.CostManagement scheduled action\" }\r\n ,\"microsoft.costmanagement/settings\": { \"SingularDisplayName\": \"Microsoft.CostManagement setting\" }\r\n ,\"microsoft.costmanagement/views\": { \"SingularDisplayName\": \"Microsoft.CostManagement view\" }\r\n ,\"microsoft.customerlockbox/requests\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox request\" }\r\n ,\"microsoft.customerlockbox/tenantoptedin\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox tenant opted in\" }\r\n ,\"microsoft.customproviders/associations\": { \"SingularDisplayName\": \"Microsoft.CustomProviders association\" }\r\n ,\"microsoft.customproviders/resourceproviders\": { \"SingularDisplayName\": \"Microsoft.CustomProviders resource provider\" }\r\n ,\"microsoft.dashboard/dashboards\": { \"SingularDisplayName\": \"Azure Monitor dashboards with Grafana\" }\r\n ,\"microsoft.dashboard/grafana\": { \"SingularDisplayName\": \"Azure Managed Grafana\" }\r\n ,\"microsoft.dataaccelerator/indexclusters\": { \"SingularDisplayName\": \"Microsoft.DataAccelerator index cluster\" }\r\n ,\"microsoft.databasefleetmanager/fleets\": { \"SingularDisplayName\": \"Database fleet manager\" }\r\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces\": { \"SingularDisplayName\": \"Fleetspaces\" }\r\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces/databases\": { \"SingularDisplayName\": \"Fleet managed database\" }\r\n ,\"microsoft.databasefleetmanager/fleets/tiers\": { \"SingularDisplayName\": \"tier\" }\r\n ,\"microsoft.databasewatcher/watchers\": { \"SingularDisplayName\": \"Database watcher\" }\r\n ,\"microsoft.databox/jobs\": { \"SingularDisplayName\": \"Azure Data Box\" }\r\n ,\"microsoft.databoxedge/databoxedgedevices\": { \"SingularDisplayName\": \"Azure Stack Edge / Data Box Gateway\" }\r\n ,\"microsoft.databricks/accessconnectors\": { \"SingularDisplayName\": \"Access Connector for Azure Databricks\" }\r\n ,\"microsoft.databricks/workspaces\": { \"SingularDisplayName\": \"Azure Databricks Service\" }\r\n ,\"microsoft.datacatalog/catalogs\": { \"SingularDisplayName\": \"Data catalog\" }\r\n ,\"microsoft.datacollaboration/workspaces\": { \"SingularDisplayName\": \"Project CI\" }\r\n ,\"microsoft.datadog/agreements\": { \"SingularDisplayName\": \"Microsoft.Datadog agreement\" }\r\n ,\"microsoft.datadog/monitors\": { \"SingularDisplayName\": \"Datadog\" }\r\n ,\"microsoft.datadog/subscriptionstatuses\": { \"SingularDisplayName\": \"Microsoft.Datadog subscription statuse\" }\r\n ,\"microsoft.datafactory/datafactories\": { \"SingularDisplayName\": \"Data factory\" }\r\n ,\"microsoft.datafactory/factories\": { \"SingularDisplayName\": \"Data factory (V2)\" }\r\n ,\"microsoft.datafactory/factories/pipelines\": { \"SingularDisplayName\": \"Data Factory pipeline\" }\r\n ,\"microsoft.datafactory/factories/triggers\": { \"SingularDisplayName\": \"Data Factory trigger\" }\r\n ,\"microsoft.datalakeanalytics/accounts\": { \"SingularDisplayName\": \"Data Lake Analytics account\" }\r\n ,\"microsoft.datalakestore/accounts\": { \"SingularDisplayName\": \"Data Lake Storage Gen1\" }\r\n ,\"microsoft.datamigration/databasemigrations\": { \"SingularDisplayName\": \"Microsoft.DataMigration database migration\" }\r\n ,\"microsoft.datamigration/migrationservices\": { \"SingularDisplayName\": \"Microsoft.DataMigration migration service\" }\r\n ,\"microsoft.datamigration/services\": { \"SingularDisplayName\": \"Azure Database Migration Service (classic)\" }\r\n ,\"microsoft.datamigration/services/projects\": { \"SingularDisplayName\": \"Azure Database Migration Project\" }\r\n ,\"microsoft.datamigration/sqlmigrationservices\": { \"SingularDisplayName\": \"Azure Database Migration Service\" }\r\n ,\"microsoft.dataprotection/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\r\n ,\"microsoft.dataprotection/resourceguards\": { \"SingularDisplayName\": \"Resource Guard\" }\r\n ,\"microsoft.datareplication/replicationfabrics\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabric\" }\r\n ,\"microsoft.datareplication/replicationfabrics/fabricagents\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agent\" }\r\n ,\"microsoft.datareplication/replicationfabrics/fabricagents/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agents operation\" }\r\n ,\"microsoft.datareplication/replicationfabrics/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics operation\" }\r\n ,\"microsoft.datareplication/replicationvaults\": { \"SingularDisplayName\": \"Data replication vault\" }\r\n ,\"microsoft.datareplication/replicationvaults/alertsettings\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults alert setting\" }\r\n ,\"microsoft.datareplication/replicationvaults/events\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults event\" }\r\n ,\"microsoft.datareplication/replicationvaults/jobs\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults job\" }\r\n ,\"microsoft.datareplication/replicationvaults/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults jobs operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnectionproxies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection proxy\" }\r\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection\" }\r\n ,\"microsoft.datareplication/replicationvaults/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private link resource\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected item\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems/recoverypoints\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items recovery point\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationextensions\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extension\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationextensions/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extensions operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policy\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policies operation\" }\r\n ,\"microsoft.datashare/accounts\": { \"SingularDisplayName\": \"Data Share\" }\r\n ,\"microsoft.dbformariadb/servers\": { \"SingularDisplayName\": \"Azure Database for MariaDB server\" }\r\n ,\"microsoft.dbformysql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for MySQL flexible server\" }\r\n ,\"microsoft.dbformysql/servers\": { \"SingularDisplayName\": \"MySQL server\" }\r\n ,\"microsoft.dbforpostgresql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for PostgreSQL flexible server\" }\r\n ,\"microsoft.dbforpostgresql/servergroupsv2\": { \"SingularDisplayName\": \"Azure Cosmos DB for PostgreSQL Cluster\" }\r\n ,\"microsoft.dbforpostgresql/servers\": { \"SingularDisplayName\": \"PostgreSQL server\" }\r\n ,\"microsoft.delegatednetwork/controller\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork controller\" }\r\n ,\"microsoft.delegatednetwork/delegatedsubnets\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork delegated subnet\" }\r\n ,\"microsoft.delegatednetwork/orchestrators\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork orchestrator\" }\r\n ,\"microsoft.dependencymap/maps\": { \"SingularDisplayName\": \"Microsoft.DependencyMap map\" }\r\n ,\"microsoft.dependencymap/maps/discoverysources\": { \"SingularDisplayName\": \"Microsoft.DependencyMap maps discovery source\" }\r\n ,\"microsoft.deploymentmanager/artifactsources\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager artifact source\" }\r\n ,\"microsoft.deploymentmanager/rollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topology\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies/services\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies service\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies/services/serviceunits\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies services service unit\" }\r\n ,\"microsoft.deploymentmanager/steps\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager step\" }\r\n ,\"microsoft.desktopvirtualization/appattachpackages\": { \"SingularDisplayName\": \"App attach package\" }\r\n ,\"microsoft.desktopvirtualization/applicationgroups\": { \"SingularDisplayName\": \"Application group\" }\r\n ,\"microsoft.desktopvirtualization/hostpools\": { \"SingularDisplayName\": \"Host pool\" }\r\n ,\"microsoft.desktopvirtualization/scalingplans\": { \"SingularDisplayName\": \"Scaling plan\" }\r\n ,\"microsoft.desktopvirtualization/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.devai/instances\": { \"SingularDisplayName\": \"Microsoft.DevAI instance\" }\r\n ,\"microsoft.devai/instances/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances experiment\" }\r\n ,\"microsoft.devai/instances/sandboxes\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandbox\" }\r\n ,\"microsoft.devai/instances/sandboxes/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandboxes experiment\" }\r\n ,\"microsoft.devcenter/devcenters\": { \"SingularDisplayName\": \"Dev center\" }\r\n ,\"microsoft.devcenter/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Dev Box definition\" }\r\n ,\"microsoft.devcenter/networkconnections\": { \"SingularDisplayName\": \"Network connection\" }\r\n ,\"microsoft.devcenter/plans\": { \"SingularDisplayName\": \"Dev center plan\" }\r\n ,\"microsoft.devcenter/projects\": { \"SingularDisplayName\": \"Project\" }\r\n ,\"microsoft.devcenter/projects/pools\": { \"SingularDisplayName\": \"Pool\" }\r\n ,\"microsoft.developmentwindows365/developmentcloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.DevelopmentWindows365 development cloud pc delegated msi\" }\r\n ,\"microsoft.devhub/iacprofiles\": { \"SingularDisplayName\": \"Infrastructure as Code Automation\" }\r\n ,\"microsoft.devhub/templates\": { \"SingularDisplayName\": \"Microsoft.DevHub template\" }\r\n ,\"microsoft.devhub/templates/versions\": { \"SingularDisplayName\": \"Microsoft.DevHub templates version\" }\r\n ,\"microsoft.devhub/workflows\": { \"SingularDisplayName\": \"Microsoft.DevHub workflow\" }\r\n ,\"microsoft.deviceonboarding/discoveryservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery service\" }\r\n ,\"microsoft.deviceonboarding/discoveryservices/ownershipvoucherpublickeys\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery services ownership voucher public key\" }\r\n ,\"microsoft.deviceonboarding/onboardingservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding service\" }\r\n ,\"microsoft.deviceonboarding/onboardingservices/policies\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding services policy\" }\r\n ,\"microsoft.deviceregistry/assetendpointprofiles\": { \"SingularDisplayName\": \"IoT Asset Endpoint Profile\" }\r\n ,\"microsoft.deviceregistry/assets\": { \"SingularDisplayName\": \"IoT Asset\" }\r\n ,\"microsoft.deviceregistry/billingcontainers\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry billing container\" }\r\n ,\"microsoft.deviceregistry/devices\": { \"SingularDisplayName\": \"IoT Device\" }\r\n ,\"microsoft.deviceregistry/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset\" }\r\n ,\"microsoft.deviceregistry/namespaces\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespace\" }\r\n ,\"microsoft.deviceregistry/namespaces/assetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/namespaces/assets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset\" }\r\n ,\"microsoft.deviceregistry/namespaces/devices\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces device\" }\r\n ,\"microsoft.deviceregistry/namespaces/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/namespaces/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset\" }\r\n ,\"microsoft.deviceregistry/schemaregistries\": { \"SingularDisplayName\": \"IoT Schema Registry\" }\r\n ,\"microsoft.deviceregistry/schemaregistries/schemas\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schema\" }\r\n ,\"microsoft.deviceregistry/schemaregistries/schemas/schemaversions\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schemas schema version\" }\r\n ,\"microsoft.devices/iothubs\": { \"SingularDisplayName\": \"IoT hub\" }\r\n ,\"microsoft.devices/provisioningservices\": { \"SingularDisplayName\": \"Azure IoT Hub Device Provisioning Service (DPS)\" }\r\n ,\"microsoft.deviceupdate/accounts\": { \"SingularDisplayName\": \"Device Update for IoT Hub\" }\r\n ,\"microsoft.deviceupdate/updateaccounts\": { \"SingularDisplayName\": \"Device Update Account\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/activedeployments\": { \"SingularDisplayName\": \"Device Update Active Deployment\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/agents\": { \"SingularDisplayName\": \"Device Update Agent\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/deployments\": { \"SingularDisplayName\": \"Device Update Deployment\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/deviceclasses\": { \"SingularDisplayName\": \"Device Update Device Class\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/updates\": { \"SingularDisplayName\": \"Device Update\" }\r\n ,\"microsoft.devops/pipelines\": { \"SingularDisplayName\": \"Microsoft.DevOps pipeline\" }\r\n ,\"microsoft.devopsinfrastructure/pools\": { \"SingularDisplayName\": \"Managed DevOps Pool\" }\r\n ,\"microsoft.devspaces/controllers\": { \"SingularDisplayName\": \"Microsoft.DevSpaces controller\" }\r\n ,\"microsoft.devtestlab/labs\": { \"SingularDisplayName\": \"DevTest lab\" }\r\n ,\"microsoft.devtestlab/labs/virtualmachines\": { \"SingularDisplayName\": \"DevTest Lab virtual machine\" }\r\n ,\"microsoft.devtestlab/schedules\": { \"SingularDisplayName\": \"Microsoft.DevTestLab schedule\" }\r\n ,\"microsoft.devtunnels/tunnelplans\": { \"SingularDisplayName\": \"Dev Tunnels Domain\" }\r\n ,\"microsoft.diagnostics/apollo\": { \"SingularDisplayName\": \"Microsoft.Diagnostics apollo\" }\r\n ,\"microsoft.digitaltwins/digitaltwinsinstances\": { \"SingularDisplayName\": \"Azure Digital Twins\" }\r\n ,\"microsoft.discovery/agents\": { \"SingularDisplayName\": \"Microsoft Discovery Agent\" }\r\n ,\"microsoft.discovery/bookshelves\": { \"SingularDisplayName\": \"Microsoft Discovery Bookshelf\" }\r\n ,\"microsoft.discovery/datacontainers\": { \"SingularDisplayName\": \"Microsoft Discovery Data Container\" }\r\n ,\"microsoft.discovery/datacontainers/dataassets\": { \"SingularDisplayName\": \"Data asset\" }\r\n ,\"microsoft.discovery/models\": { \"SingularDisplayName\": \"Microsoft Discovery Model\" }\r\n ,\"microsoft.discovery/storages\": { \"SingularDisplayName\": \"Microsoft Discovery Storage\" }\r\n ,\"microsoft.discovery/supercomputers\": { \"SingularDisplayName\": \"Microsoft Discovery Supercomputer\" }\r\n ,\"microsoft.discovery/supercomputers/nodepools\": { \"SingularDisplayName\": \"Nodepool\" }\r\n ,\"microsoft.discovery/tools\": { \"SingularDisplayName\": \"Microsoft Discovery Tool\" }\r\n ,\"microsoft.discovery/workflows\": { \"SingularDisplayName\": \"Microsoft Discovery Workflow\" }\r\n ,\"microsoft.discovery/workspaces\": { \"SingularDisplayName\": \"Microsoft Discovery Workspace\" }\r\n ,\"microsoft.discovery/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft Discovery Project\" }\r\n ,\"microsoft.documentdb/cassandraclusters\": { \"SingularDisplayName\": \"Azure Managed Instance for Apache Cassandra\" }\r\n ,\"microsoft.documentdb/databaseaccounts\": { \"SingularDisplayName\": \"Cosmos DB account\" }\r\n ,\"microsoft.documentdb/fleets\": { \"SingularDisplayName\": \"Azure Cosmos DB Fleet\" }\r\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccounts\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\r\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccountswithlocations\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\r\n ,\"microsoft.documentdb/mongoclusters\": { \"SingularDisplayName\": \"Azure Cosmos DB for MongoDB (vCore)\" }\r\n ,\"microsoft.documentdb/throughputpools\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pool\" }\r\n ,\"microsoft.documentdb/throughputpools/throughputpoolaccounts\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pools throughput pool account\" }\r\n ,\"microsoft.domainregistration/domains\": { \"SingularDisplayName\": \"App Service Domain\" }\r\n ,\"microsoft.domainregistration/topleveldomains\": { \"SingularDisplayName\": \"Microsoft.DomainRegistration top level domain\" }\r\n ,\"microsoft.durabletask/namespaces\": { \"SingularDisplayName\": \"Microsoft.DurableTask namespace\" }\r\n ,\"microsoft.durabletask/namespaces/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\r\n ,\"microsoft.durabletask/schedulers\": { \"SingularDisplayName\": \"Durable Task Scheduler\" }\r\n ,\"microsoft.durabletask/schedulers/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\r\n ,\"microsoft.dynamics365fraudprotection/instances\": { \"SingularDisplayName\": \"Microsoft.Dynamics365FraudProtection instance\" }\r\n ,\"microsoft.easm/workspaces\": { \"SingularDisplayName\": \"Microsoft Defender EASM\" }\r\n ,\"microsoft.edge/configurations\": { \"SingularDisplayName\": \"Site configuration\" }\r\n ,\"microsoft.edge/configurations/arcgatewayconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations arc gateway configuration\" }\r\n ,\"microsoft.edge/configurations/connectivityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations connectivity configuration\" }\r\n ,\"microsoft.edge/configurations/dynamicconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configuration\" }\r\n ,\"microsoft.edge/configurations/dynamicconfigurations/versions\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configurations version\" }\r\n ,\"microsoft.edge/configurations/networkconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations network configuration\" }\r\n ,\"microsoft.edge/configurations/securityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations security configuration\" }\r\n ,\"microsoft.edge/configurations/timeserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations time server configuration\" }\r\n ,\"microsoft.edge/connectivitystatuses\": { \"SingularDisplayName\": \"Microsoft.Edge connectivity statuse\" }\r\n ,\"microsoft.edge/disconnectedoperations\": { \"SingularDisplayName\": \"Azure Local - disconnected operations\" }\r\n ,\"microsoft.edge/siteawareresourcetypes\": { \"SingularDisplayName\": \"Microsoft.Edge site aware resource type\" }\r\n ,\"microsoft.edge/sites\": { \"SingularDisplayName\": \"Site manager - Azure Arc\" }\r\n ,\"microsoft.edge/updates\": { \"SingularDisplayName\": \"Microsoft.Edge update\" }\r\n ,\"microsoft.edgemarketplace/offers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace offer\" }\r\n ,\"microsoft.edgemarketplace/publishers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace publisher\" }\r\n ,\"microsoft.edgeorder/addresses\": { \"SingularDisplayName\": \"Azure Edge Hardware Center Address\" }\r\n ,\"microsoft.edgeorder/bootstrapconfigurations\": { \"SingularDisplayName\": \"Site Key\" }\r\n ,\"microsoft.edgeorder/orderitems\": { \"SingularDisplayName\": \"Azure Edge Hardware Center\" }\r\n ,\"microsoft.edgeorder/virtual_orderitems\": { \"SingularDisplayName\": \"Device\" }\r\n ,\"microsoft.edgezones/extendedzones\": { \"SingularDisplayName\": \"Microsoft.EdgeZones extended zone\" }\r\n ,\"microsoft.education/grants\": { \"SingularDisplayName\": \"Microsoft.Education grant\" }\r\n ,\"microsoft.education/labs\": { \"SingularDisplayName\": \"Microsoft.Education lab\" }\r\n ,\"microsoft.education/labs/joinrequests\": { \"SingularDisplayName\": \"Microsoft.Education labs join request\" }\r\n ,\"microsoft.education/labs/students\": { \"SingularDisplayName\": \"Microsoft.Education labs student\" }\r\n ,\"microsoft.education/studentlabs\": { \"SingularDisplayName\": \"Microsoft.Education student lab\" }\r\n ,\"microsoft.elastic/monitors\": { \"SingularDisplayName\": \"Elastic Cloud Resource\" }\r\n ,\"microsoft.elasticsan/elasticsans\": { \"SingularDisplayName\": \"Elastic SAN\" }\r\n ,\"microsoft.energydataplatform/energyservices\": { \"SingularDisplayName\": \"Microsoft.EnergyDataPlatform energy service\" }\r\n ,\"microsoft.enterpriseknowledgegraph/services\": { \"SingularDisplayName\": \"Microsoft.EnterpriseKnowledgeGraph service\" }\r\n ,\"microsoft.enterprisesupport/enterprisesupports\": { \"SingularDisplayName\": \"Microsoft.EnterpriseSupport enterprise support\" }\r\n ,\"microsoft.eventgrid/domains\": { \"SingularDisplayName\": \"Event Grid Domain\" }\r\n ,\"microsoft.eventgrid/domains/topics\": { \"SingularDisplayName\": \"Event Grid Domain Topic\" }\r\n ,\"microsoft.eventgrid/eventsubscriptions\": { \"SingularDisplayName\": \"Microsoft.EventGrid event subscription\" }\r\n ,\"microsoft.eventgrid/extensiontopics\": { \"SingularDisplayName\": \"Event Grid extension topic\" }\r\n ,\"microsoft.eventgrid/namespaces\": { \"SingularDisplayName\": \"Event Grid Namespace\" }\r\n ,\"microsoft.eventgrid/namespaces/topics\": { \"SingularDisplayName\": \"Event Grid Namespace Topic\" }\r\n ,\"microsoft.eventgrid/namespaces/topics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Subscription\" }\r\n ,\"microsoft.eventgrid/namespaces/topicspaces\": { \"SingularDisplayName\": \"Event Grid Topic Space\" }\r\n ,\"microsoft.eventgrid/partnerconfigurations\": { \"SingularDisplayName\": \"Event Grid Partner Configuration\" }\r\n ,\"microsoft.eventgrid/partnerdestinations\": { \"SingularDisplayName\": \"Event Grid Partner Destination\" }\r\n ,\"microsoft.eventgrid/partnernamespaces\": { \"SingularDisplayName\": \"Event Grid Partner Namespace\" }\r\n ,\"microsoft.eventgrid/partnernamespaces/channels\": { \"SingularDisplayName\": \"Event Grid Channel\" }\r\n ,\"microsoft.eventgrid/partnerregistrations\": { \"SingularDisplayName\": \"Event Grid Partner Registration\" }\r\n ,\"microsoft.eventgrid/partnertopics\": { \"SingularDisplayName\": \"Event Grid Partner Topic\" }\r\n ,\"microsoft.eventgrid/systemtopics\": { \"SingularDisplayName\": \"Event Grid System Topic\" }\r\n ,\"microsoft.eventgrid/systemtopics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Grid Subscriptions\" }\r\n ,\"microsoft.eventgrid/topics\": { \"SingularDisplayName\": \"Event Grid Topic\" }\r\n ,\"microsoft.eventgrid/topictypes\": { \"SingularDisplayName\": \"Microsoft.EventGrid topic type\" }\r\n ,\"microsoft.eventgrid/verifiedpartners\": { \"SingularDisplayName\": \"Microsoft.EventGrid verified partner\" }\r\n ,\"microsoft.eventhub/clusters\": { \"SingularDisplayName\": \"Event Hubs Cluster\" }\r\n ,\"microsoft.eventhub/namespaces\": { \"SingularDisplayName\": \"Event Hubs namespace\" }\r\n ,\"microsoft.eventhub/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Event Hubs Geo-DR Alias\" }\r\n ,\"microsoft.eventhub/namespaces/eventhubs\": { \"SingularDisplayName\": \"Event Hubs Instance\" }\r\n ,\"microsoft.eventhub/namespaces/providers/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\r\n ,\"microsoft.eventhub/namespaces/schemagroups\": { \"SingularDisplayName\": \"Schema Group\" }\r\n ,\"microsoft.experimentation/experimentworkspaces\": { \"SingularDisplayName\": \"Experiment Workspace\" }\r\n ,\"microsoft.extendedlocation/customlocations\": { \"SingularDisplayName\": \"Custom location\" }\r\n ,\"microsoft.fabric/capacities\": { \"SingularDisplayName\": \"Fabric Capacity\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/operationresults\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric operation result\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private endpoint connection\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private link resource\" }\r\n ,\"microsoft.fairfieldgardens/deviceprovisioningstates\": { \"SingularDisplayName\": \"Microsoft.FairfieldGardens device provisioning state\" }\r\n ,\"microsoft.fairfieldgardens/provisioningresources\": { \"SingularDisplayName\": \"Fairfield Gardens\" }\r\n ,\"microsoft.fairfieldgardens/provisioningresources/provisioningpolicies\": { \"SingularDisplayName\": \"Provisioning policy\" }\r\n ,\"microsoft.falcon/namespaces\": { \"SingularDisplayName\": \"Microsoft.Falcon namespace\" }\r\n ,\"microsoft.features/featureprovidernamespaces/featureconfigurations\": { \"SingularDisplayName\": \"Preview features\" }\r\n ,\"microsoft.fidalgo/devcenters\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenter\" }\r\n ,\"microsoft.fidalgo/devcenters/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters attachednetwork\" }\r\n ,\"microsoft.fidalgo/devcenters/catalogs\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalog\" }\r\n ,\"microsoft.fidalgo/devcenters/catalogs/items\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalogs item\" }\r\n ,\"microsoft.fidalgo/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters devboxdefinition\" }\r\n ,\"microsoft.fidalgo/devcenters/environmenttypes\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters environment type\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters gallery\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries/images\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries image\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries/images/versions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries images version\" }\r\n ,\"microsoft.fidalgo/devcenters/mappings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters mapping\" }\r\n ,\"microsoft.fidalgo/machinedefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo machinedefinition\" }\r\n ,\"microsoft.fidalgo/networksettings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksetting\" }\r\n ,\"microsoft.fidalgo/networksettings/healthchecks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksettings healthcheck\" }\r\n ,\"microsoft.fidalgo/projects\": { \"SingularDisplayName\": \"Microsoft.Fidalgo project\" }\r\n ,\"microsoft.fidalgo/projects/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects attachednetwork\" }\r\n ,\"microsoft.fidalgo/projects/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects devboxdefinition\" }\r\n ,\"microsoft.fidalgo/projects/environments\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects environment\" }\r\n ,\"microsoft.fidalgo/projects/pools\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects pool\" }\r\n ,\"microsoft.fileshares/fileshares\": { \"SingularDisplayName\": \"File share\" }\r\n ,\"microsoft.fluidrelay/fluidrelayservers\": { \"SingularDisplayName\": \"Fluid Relay\" }\r\n ,\"microsoft.footprintmonitoring/profiles\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profile\" }\r\n ,\"microsoft.footprintmonitoring/profiles/experiments\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles experiment\" }\r\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoint\" }\r\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints/conditions\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoints condition\" }\r\n ,\"microsoft.gallery/myareas/galleryitems\": { \"SingularDisplayName\": \"Template\" }\r\n ,\"microsoft.genomics/accounts\": { \"SingularDisplayName\": \"Genomics account\" }\r\n ,\"microsoft.graph/azureadapplication\": { \"SingularDisplayName\": \"Entra application\" }\r\n ,\"microsoft.graph/azureadapplicationprototype\": { \"SingularDisplayName\": \"Microsoft.Graph Azure ad application prototype\" }\r\n ,\"microsoft.graphservices/accounts\": { \"SingularDisplayName\": \"Metered API account\" }\r\n ,\"microsoft.guestconfiguration/guestconfigurationassignments\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignment\" }\r\n ,\"microsoft.guestconfiguration/guestconfigurationassignments/reports\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignments report\" }\r\n ,\"microsoft.hanaonazure/hanainstances\": { \"SingularDisplayName\": \"SAP HANA on Azure\" }\r\n ,\"microsoft.hanaonazure/sapmonitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP Solutions (classic)\" }\r\n ,\"microsoft.hardware/orders\": { \"SingularDisplayName\": \"Microsoft.Hardware order\" }\r\n ,\"microsoft.hardwaresecuritymodules/cloudhsmclusters\": { \"SingularDisplayName\": \"Azure Cloud HSM\" }\r\n ,\"microsoft.hdinsight/clusterpools\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster pool\" }\r\n ,\"microsoft.hdinsight/clusterpools/clusters\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster\" }\r\n ,\"microsoft.hdinsight/clusterpools/clusters/instanceviews\": { \"SingularDisplayName\": \"Microsoft.HDInsight clusterpools clusters instance view\" }\r\n ,\"microsoft.hdinsight/clusters\": { \"SingularDisplayName\": \"HDInsight cluster\" }\r\n ,\"microsoft.healthbot/healthbots\": { \"SingularDisplayName\": \"Healthcare agent service\" }\r\n ,\"microsoft.healthcareapis/services\": { \"SingularDisplayName\": \"Azure API for FHIR\" }\r\n ,\"microsoft.healthcareapis/workspaces\": { \"SingularDisplayName\": \"Health Data Services workspace\" }\r\n ,\"microsoft.healthcareapis/workspaces/dicomservices\": { \"SingularDisplayName\": \"DICOM service\" }\r\n ,\"microsoft.healthcareapis/workspaces/fhirservices\": { \"SingularDisplayName\": \"FHIR service\" }\r\n ,\"microsoft.healthcareapis/workspaces/iotconnectors\": { \"SingularDisplayName\": \"MedTech service\" }\r\n ,\"microsoft.healthdataaiservices/deidservices\": { \"SingularDisplayName\": \"De-identification Service\" }\r\n ,\"microsoft.healthmodel/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\r\n ,\"microsoft.healthplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.HealthPlatform account\" }\r\n ,\"microsoft.help/diagnostics\": { \"SingularDisplayName\": \"Microsoft.Help diagnostic\" }\r\n ,\"microsoft.help/selfhelp\": { \"SingularDisplayName\": \"Microsoft.Help self help\" }\r\n ,\"microsoft.help/simplifiedsolutions\": { \"SingularDisplayName\": \"Microsoft.Help simplified solution\" }\r\n ,\"microsoft.help/solutions\": { \"SingularDisplayName\": \"Microsoft.Help solution\" }\r\n ,\"microsoft.help/troubleshooters\": { \"SingularDisplayName\": \"Microsoft.Help troubleshooter\" }\r\n ,\"microsoft.hpcworkbench/instances\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instance\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chamber\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/accessprofiles\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers access profile\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/filerequests\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file request\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/files\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/storages\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers storage\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/workloads\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers workload\" }\r\n ,\"microsoft.hpcworkbench/instances/consortiums\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances consortium\" }\r\n ,\"microsoft.hybridcloud/cloudconnections\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connection\" }\r\n ,\"microsoft.hybridcloud/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connector\" }\r\n ,\"microsoft.hybridcompute/arcgatewayassociatedresources\": { \"SingularDisplayName\": \"Arc gateway associated resource\" }\r\n ,\"microsoft.hybridcompute/arcserverwithwac\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/gateways\": { \"SingularDisplayName\": \"Arc gateway\" }\r\n ,\"microsoft.hybridcompute/licenses\": { \"SingularDisplayName\": \"Extended Security Updates - Windows Server 2012/R2\" }\r\n ,\"microsoft.hybridcompute/machines\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machines/microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\r\n ,\"microsoft.hybridcompute/machines/microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\r\n ,\"microsoft.hybridcompute/machines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.hybridcompute/machinesesu\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinespaygo\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinessoftwareassurance\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinessovereign\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Arc Private Link Scope\" }\r\n ,\"microsoft.hybridcompute/settings\": { \"SingularDisplayName\": \"Microsoft.HybridCompute setting\" }\r\n ,\"microsoft.hybridconnectivity/endpoints\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoint\" }\r\n ,\"microsoft.hybridconnectivity/endpoints/serviceconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoints service configuration\" }\r\n ,\"microsoft.hybridconnectivity/publiccloudconnectors\": { \"SingularDisplayName\": \"Multicloud connector\" }\r\n ,\"microsoft.hybridconnectivity/solutionconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configuration\" }\r\n ,\"microsoft.hybridconnectivity/solutionconfigurations/inventory\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configurations inventory\" }\r\n ,\"microsoft.hybridconnectivity/solutiontypes\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution type\" }\r\n ,\"microsoft.hybridcontainerservice/kubernetesversions\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService kubernetes version\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instance\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/agentpools\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances agent pool\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances hybrid identity metadata\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances upgrade profile\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusters\": { \"SingularDisplayName\": \"Kubernetes hybrid - Azure Arc\" }\r\n ,\"microsoft.hybridcontainerservice/skus\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService SKU\" }\r\n ,\"microsoft.hybridcontainerservice/storagespaces\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService storage space\" }\r\n ,\"microsoft.hybridcontainerservice/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService virtual network\" }\r\n ,\"microsoft.hybriddata/datamanagers\": { \"SingularDisplayName\": \"Microsoft.HybridData data manager\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data service\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definition\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions/jobs\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definitions job\" }\r\n ,\"microsoft.hybriddata/datamanagers/datastores\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store\" }\r\n ,\"microsoft.hybriddata/datamanagers/datastoretypes\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store type\" }\r\n ,\"microsoft.hybriddata/datamanagers/publickeys\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers public key\" }\r\n ,\"microsoft.hybridnetwork/configurationgroupvalues\": { \"SingularDisplayName\": \"Configuration Group Value\" }\r\n ,\"microsoft.hybridnetwork/devices\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Device\" }\r\n ,\"microsoft.hybridnetwork/networkfunctions\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Network Function\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publisher\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/artifactstores\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers artifact store\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers configuration group schema\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition group\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition groups network function definition version\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design group\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design groups network service design version\" }\r\n ,\"microsoft.hybridnetwork/publishers\": { \"SingularDisplayName\": \"Publisher\" }\r\n ,\"microsoft.hybridnetwork/publishers/artifactstores\": { \"SingularDisplayName\": \"Publisher Artifact Store\" }\r\n ,\"microsoft.hybridnetwork/publishers/artifactstores/artifactmanifests\": { \"SingularDisplayName\": \"Publisher Artifact Manifest\" }\r\n ,\"microsoft.hybridnetwork/publishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Configuration Group Schema\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Network Function Definition\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Network Function Definition Version\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Network Service Design\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Network Service Design Version\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management container\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rolloutsequences\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout sequence\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rollouttiers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout tier\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specification\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollout\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts/statuses\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollouts statuse\" }\r\n ,\"microsoft.hybridnetwork/sitenetworkservices\": { \"SingularDisplayName\": \"Site Network Service\" }\r\n ,\"microsoft.hybridnetwork/sites\": { \"SingularDisplayName\": \"Site\" }\r\n })[tolower(id)]\r\n}\r\n", - "$fxv#10": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Prices |=========================================================================================================\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='Transforms Prices_raw into FOCUS 1.2.', folder='Prices')\r\nPrices_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n let prices = materialize(\r\n Prices_raw\r\n | extend PricingCurrency = coalesce(Currency, CurrencyCode) // CurrencyCode last as a fallback only\r\n | extend x_SkuId = coalesce(SkuId, SkuID)\r\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\r\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\r\n | extend x_SkuTerm = isoMonths(Term)\r\n | project-rename\r\n SkuMeter = MeterName,\r\n x_BaseUnitPrice = BasePrice,\r\n x_EffectivePeriodEnd = EffectiveEndDate,\r\n x_EffectivePeriodStart = EffectiveStartDate,\r\n x_PricingUnitDescription = UnitOfMeasure,\r\n x_SkuIncludedQuantity = IncludedQuantity,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuMeterType = MeterType,\r\n x_SkuOfferId = OfferID,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPriceType = PriceType,\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTier = TierMinimumUnits\r\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, real(null)) // UnitPrice for savings plan is not the on-demand unit price\r\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, real(null)) // MarketPrice for savings plan is not the list price\r\n | extend ChargeCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Usage',\r\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\r\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\r\n ''\r\n )\r\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\r\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\r\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\r\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\r\n //\r\n // Get latest ingested row based on the unique ID\r\n | extend x_IngestionTime = ingestion_time()\r\n );\r\n //\r\n // Meters for reservations and savings plans to identify commitment eligibility\r\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\r\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\r\n //\r\n // Copy list/base/contracted prices from on-demand SKUs\r\n prices\r\n | where x_SkuPriceType == 'SavingsPlan'\r\n // If we use join, specify the shuffle key\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\r\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\r\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\r\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\r\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\r\n //\r\n // Set CommitmentDiscountCategory for reuse\r\n | extend CommitmentDiscountCategory = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Usage',\r\n x_SkuPriceType == 'SavingsPlan', 'Spend',\r\n ''\r\n )\r\n //\r\n // Calculate commitment discount eligibility\r\n // TODO: Would a join be faster?\r\n // TODO: Check this to ensure it's correct\r\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\r\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\r\n //\r\n // TODO: Implement x_CommitmentDiscountNormalizedRatio\r\n | extend x_CommitmentDiscountNormalizedRatio = real(null)\r\n //\r\n // Add PricingUnit and x_PricingBlockSize\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\r\n | lookup kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n //\r\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, real(null)) // Savings plan prices are for the effective price, not the contracted price\r\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\r\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\r\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\r\n | project\r\n BillingAccountId = tolower(case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n BillingAccountId startswith '/', BillingAccountId,\r\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\r\n )),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\r\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\r\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\r\n ''\r\n ),\r\n CommitmentDiscountUnit = case(\r\n isempty(CommitmentDiscountCategory), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), PricingUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', PricingUnit),\r\n ''\r\n ),\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed',\r\n ''\r\n ),\r\n PricingCurrency,\r\n PricingUnit,\r\n SkuId = coalesce(ProductId, ProductID),\r\n SkuMeter,\r\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n strlen(x_BillingAccountId) > 32, 'MCA',\r\n strlen(x_BillingAccountId) < 32, 'EA',\r\n 'Unknown'\r\n ),\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\r\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory = case(\r\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_SkuDescription = Product,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\r\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\r\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\r\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\r\n}\r\n\r\n// Prices_final_v1_2 table\r\n.create-merge table Prices_final_v1_2 (\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n ChargeCategory: string,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountType: string,\r\n CommitmentDiscountUnit: string,\r\n ContractedUnitPrice: real,\r\n ListUnitPrice: real,\r\n PricingCategory: string,\r\n PricingCurrency: string, // Azure\r\n PricingUnit: string,\r\n SkuId: string,\r\n SkuMeter: string, // Azure\r\n SkuPriceId: string,\r\n SkuPriceIdv2: string, // Hubs add-on\r\n x_BaseUnitPrice: real, // Azure\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure MCA\r\n x_BillingProfileId: string, // Azure MCA\r\n x_CommitmentDiscountNormalizedRatio: real, // Hubs add-on\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_ContractedUnitPriceDiscount: real, // Hubs add-on\r\n x_ContractedUnitPriceDiscountPercent: real, // Hubs add-on\r\n x_EffectivePeriodEnd: datetime, // Azure\r\n x_EffectivePeriodStart: datetime, // Azure\r\n x_EffectiveUnitPrice: real, // Azure\r\n x_EffectiveUnitPriceDiscount: real, // Hubs add-on\r\n x_EffectiveUnitPriceDiscountPercent: real, // Hubs add-on\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_PricingBlockSize: real, // Hubs add-on\r\n x_PricingSubcategory: string, // Hubs add-on\r\n x_PricingUnitDescription: string, // Azure\r\n x_SkuDescription: string, // Azure\r\n x_SkuId: string, // Azure\r\n x_SkuIncludedQuantity: real, // Azure EA\r\n x_SkuMeterCategory: string, // Azure\r\n x_SkuMeterId: string, // Azure\r\n x_SkuMeterSubcategory: string, // Azure\r\n x_SkuMeterType: string, // Azure\r\n x_SkuPriceType: string, // Azure\r\n x_SkuProductId: string, // Azure\r\n x_SkuRegion: string, // Azure\r\n x_SkuServiceFamily: string, // Azure\r\n x_SkuOfferId: string, // Azure EA\r\n x_SkuPartNumber: string, // Azure EA\r\n x_SkuTerm: int, // Azure\r\n x_SkuTier: real, // Azure MCA\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_TotalUnitPriceDiscount: real, // Hubs add-on\r\n x_TotalUnitPriceDiscountPercent: real // Hubs add-on\r\n)\r\n\r\n// Update policy for Prices_raw -> Prices_final_v1_2\r\n.alter table Prices_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Prices_raw\",\r\n \"Query\": \"Prices_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Cost and usage |=================================================================================================\r\n// Supported versions:\r\n// - MS: 1.2-preview, 1.0, 1.0-preview(v1)\r\n// https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0\r\n// https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024\r\n// https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 \r\n// https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All costs transformed to FOCUS 1.2.', folder='Costs')\r\nCosts_transform_v1_2()\r\n{\r\n let checkString = (column: string, oldValue: string, newValue: string) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n let checkInt = (column: string, oldValue: int, newValue: int) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n let checkReal = (column: string, oldValue: real, newValue: real) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n Costs_raw\r\n //\r\n // Dedupe rows\r\n | extend x_IngestionTime = ingestion_time()\r\n | extend x_ChargeId = ''\r\n // TODO: Consider adding a unique charge ID per row\r\n // hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // // 1. Resource hierarchy (including resource name), highest to lowest\r\n // BillingAccountId,\r\n // x_InvoiceSectionId,\r\n // x_AccountOwnerId,\r\n // SubAccountId,\r\n // x_ResourceGroupName,\r\n // ResourceName,\r\n // // 2. Resource details\r\n // ResourceId,\r\n // RegionId,\r\n // Tags,\r\n // CommitmentDiscountId,\r\n // x_CostCenter,\r\n // // 4. Meter details\r\n // SkuPriceId,\r\n // x_SkuMeterId,\r\n // x_SkuPartNumber,\r\n // x_SkuOfferId,\r\n // x_SkuDetails,\r\n // // 5. Date\r\n // ChargePeriodStart\r\n // ))\r\n //\r\n // Identify data quality issues\r\n // TODO: Remove x_SourceChanges in v1_3 (or later)\r\n | extend x_SourceChanges = trim_end(',', strcat(\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\r\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\r\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\r\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\r\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\r\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\r\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\r\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\r\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\r\n 'XEffectiveUnitPriceRoundingError,', ''),\r\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\r\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\r\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\r\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\r\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\r\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\r\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\r\n ))\r\n //\r\n // Handle provider columns that moved to FOCUS\r\n | extend PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency)\r\n //\r\n // Backup original prices/costs before the merge\r\n | extend old_ContractedCost = ContractedCost\r\n | extend old_ContractedUnitPrice = ContractedUnitPrice\r\n | extend old_ListCost = ListCost\r\n | extend old_ListUnitPrice = ListUnitPrice\r\n | extend old_x_EffectiveUnitPrice = x_EffectiveUnitPrice\r\n //\r\n // Fix columns needed in other changes\r\n | extend old_ProviderName = ProviderName, ProviderName = case(\r\n isnotempty(ProviderName), ProviderName,\r\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\r\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\r\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\r\n ''\r\n )\r\n //\r\n // Identify source\r\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\r\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\r\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\r\n ''\r\n ))\r\n // Append version check error code\r\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\r\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\r\n )\r\n //\r\n // Fix quantities\r\n | extend old_PricingQuantity = PricingQuantity, PricingQuantity = case(\r\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\r\n PricingQuantity\r\n )\r\n | extend old_ConsumedQuantity = ConsumedQuantity, ConsumedQuantity = case(\r\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\r\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\r\n ConsumedQuantity\r\n )\r\n //\r\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\r\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\r\n and (isempty(ListUnitPrice) or isempty(ContractedUnitPrice) or ListUnitPrice == 0 or ContractedUnitPrice == 0)\r\n and x_EffectiveUnitPrice != 0\r\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\r\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\r\n | as allCosts\r\n | where tmp_MissingPrices\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | as costsWithMissingPrices\r\n | join kind=leftouter (\r\n Prices_final_v1_2\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\r\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\r\n ) on tmp_ReservationPriceLookupKey\r\n //\r\n // Select the best price to use for each row\r\n | extend x_EffectiveUnitPrice = case(\r\n // If price is a rounding error away from the billed price, use the billed price\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\r\n // If price is a rounding error away from the contracted price, use the contracted price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ContractedUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\r\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ListUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // Otherwise, assume the contracted price is the same as list price to support aggregations\r\n ContractedUnitPrice\r\n )\r\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\r\n | extend ContractedCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\r\n // ContractedCost is 0 in all other scenarios...\r\n // If 0 and there's a billed cost and prices are the same, use BilledCost\r\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\r\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\r\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume EffectiveCost\r\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ContractedCost\r\n )\r\n | extend ListCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\r\n // ListCost is 0 in all other scenarios...\r\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\r\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume ContractedCost\r\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ListCost\r\n )\r\n // Merge the rest of the unmodified cost records and remove excess columns\r\n | union (allCosts | where not(tmp_MissingPrices))\r\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\r\n //\r\n | extend SkuPriceDetails = parse_json(SkuPriceDetails)\r\n | extend Tags = parse_json(Tags)\r\n | extend x_SkuDetails = parse_json(x_SkuDetails)\r\n //\r\n // Handle FOCUS 1.0-preview\r\n | extend old_ChargeSubcategory = ChargeSubcategory\r\n | extend old_ChargeCategory = ChargeCategory, ChargeCategory = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Credit', 'Credit',\r\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\r\n ChargeCategory\r\n )\r\n | extend old_ChargeClass = ChargeClass, ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass)\r\n //\r\n // Populate CapacityReservationId when not specified\r\n | extend CapacityReservationId = coalesce(CapacityReservationId, tostring(coalesce(x_SkuDetails.VMCapacityReservationId, SkuPriceDetails.VMCapacityReservationId, SkuPriceDetails.x_VMCapacityReservationId)))\r\n | extend old_CapacityReservationStatus = CapacityReservationStatus, CapacityReservationStatus = case(\r\n isempty(CapacityReservationId), '',\r\n isnotempty(CapacityReservationStatus), CapacityReservationStatus,\r\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\r\n 'Used'\r\n )\r\n //\r\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\r\n | extend old_ChargeFrequency = ChargeFrequency, ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency)\r\n //\r\n // Commitment discounts\r\n | extend x_CommitmentDiscountNormalizedRatio = case(\r\n // Calculate from CommitmentDiscountQuantity, if specified\r\n isnotempty(CommitmentDiscountQuantity) and CommitmentDiscountQuantity != 0, CommitmentDiscountQuantity / PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\r\n // Not applicable\r\n isempty(CommitmentDiscountStatus), real(null),\r\n // Parse from SKU details if not specified explicitly\r\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, SkuPriceDetails.RINormalizationRatio, SkuPriceDetails.x_RINormalizationRatio, dynamic(1)))\r\n )\r\n | extend old_CommitmentDiscountQuantity = CommitmentDiscountQuantity, CommitmentDiscountQuantity = case(\r\n // FOCUS 1.2\r\n isnotempty(CommitmentDiscountQuantity), CommitmentDiscountQuantity,\r\n // FOCUS 1.0-preview, 1.0\r\n isempty(CommitmentDiscountStatus), real(null),\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\r\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\r\n real(null)\r\n )\r\n | extend old_CommitmentDiscountUnit = CommitmentDiscountUnit, CommitmentDiscountUnit = case(\r\n // FOCUS 1.2\r\n isnotempty(CommitmentDiscountUnit), CommitmentDiscountUnit,\r\n // FOCUS 1.0\r\n isempty(CommitmentDiscountQuantity), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\r\n ''\r\n )\r\n | extend old_CommitmentDiscountStatus = CommitmentDiscountStatus, CommitmentDiscountStatus = case(\r\n // FOCUS 1.0+\r\n isnotempty(CommitmentDiscountStatus), CommitmentDiscountStatus,\r\n // FOCUS 1.0-preview\r\n ChargeSubcategory == 'Used Commitment', 'Used',\r\n ChargeSubcategory == 'Unused Commitment', 'Unused',\r\n ''\r\n )\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n //\r\n // Pricing\r\n | extend old_x_AmortizationClass = x_AmortizationClass, x_AmortizationClass = case(\r\n // FOCUS 1.2\r\n isnotempty(x_AmortizationClass), x_AmortizationClass,\r\n // FOCUS 1.0-preview+\r\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\r\n ''\r\n )\r\n | extend old_PricingCategory = PricingCategory, PricingCategory = case(\r\n // FOCUS 1.0+\r\n isnotempty(PricingCategory), PricingCategory,\r\n // FOCUS 1.0-preview\r\n PricingCategory == 'On-Demand', 'Standard',\r\n PricingCategory == 'Commitment-Based', 'Committed',\r\n ''\r\n )\r\n //\r\n // Commitment discount utilization\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n //\r\n // BUG: Fix ContractedCost that has bad values\r\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\r\n //\r\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\r\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), real(null))\r\n | extend old_ConsumedUnit = ConsumedUnit, ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\r\n //\r\n // Convert IDs to lowercase for consistency\r\n | extend BillingAccountId = tolower(BillingAccountId)\r\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\r\n //\r\n // BUG: Remove EffectiveCost for commitment discount purchases\r\n | extend old_EffectiveCost = EffectiveCost, EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), EffectiveCost)\r\n | extend old_x_EffectiveCostInUsd = x_EffectiveCostInUsd, x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), x_EffectiveCostInUsd)\r\n //\r\n // Clean up resource columns\r\n | extend old_ResourceId = ResourceId, ResourceId = case(\r\n isnotempty(ResourceId), ResourceId,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\r\n ResourceId\r\n )\r\n | extend old_ResourceName = ResourceName, ResourceName = tolower(case(\r\n isnotempty(ResourceName), ResourceName,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\r\n ResourceName\r\n ))\r\n | extend old_x_ResourceType = x_ResourceType, x_ResourceType = case(\r\n isnotempty(x_ResourceType), x_ResourceType,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\r\n x_ResourceType\r\n )\r\n | extend old_ResourceType = ResourceType, ResourceType = case(\r\n // Use existing resource type display name unless it's an internal resource type ID\r\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\r\n // Use CommitmentDiscountType for commitment discount purchases\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\r\n // Look up display name from internal type\r\n isnotempty(x_ResourceType), coalesce(tostring(resource_type(x_ResourceType).SingularDisplayName), ResourceType, x_ResourceType),\r\n ResourceType\r\n )\r\n //\r\n // Handle missing values\r\n | extend old_PublisherName = PublisherName, PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, '')\r\n //\r\n // Handle FOCUS 1.0-preview Region column\r\n | extend old_Region = Region\r\n | extend old_RegionId = RegionId, RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region))\r\n | extend RegionName = coalesce(RegionName, Region)\r\n //\r\n // SKU properties\r\n | extend x_SkuCoreCount = toint(coalesce(SkuPriceDetails.CoreCount, SkuPriceDetails.x_VCPUs, x_SkuDetails.VCPUs, SkuPriceDetails.x_VCores, x_SkuDetails.VCores, SkuPriceDetails.x_vCores, x_SkuDetails.vCores))\r\n | extend x_SkuInstanceType = tostring(coalesce(SkuPriceDetails.InstanceType, SkuPriceDetails.x_ServiceType, x_SkuDetails.ServiceType, SkuPriceDetails.x_ServerSku, x_SkuDetails.ServerSku))\r\n | extend x_SkuOperatingSystem = case(\r\n isnotempty(SkuPriceDetails.OperatingSystem), SkuPriceDetails.OperatingSystem,\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Canonical', 'Linux',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType)\r\n )\r\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\r\n | extend SkuPriceDetails = case(\r\n // FOCUS 1.2\r\n isnotempty(SkuPriceDetails), SkuPriceDetails,\r\n // FOCUS 1.0-preview, 1.0\r\n parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\r\n // Prefix all keys with x_ first to avoid double-prefixing\r\n , @'([\\{,])\"', @'\\1\"x_')\r\n // CoreCount for number of CPUs/vCPUs/cores/vCores\r\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\r\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\r\n // TODO: DiskSpace for disk size in GiB\r\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\r\n // TODO: GpuCount for the number of GPUs\r\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\r\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\r\n // TODO: InstanceSeries for the size family/series\r\n // TODO: MemorySize for the RAM in GiB\r\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\r\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\r\n // OperatingSystem for the OS name\r\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\r\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\r\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\r\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\r\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\r\n )\r\n )\r\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\r\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\r\n SkuPriceDetails)\r\n //\r\n // Azure Hybrid Benefit\r\n | extend tmp_SqlAhb = tolower(coalesce(x_SkuDetails.AHB, SkuPriceDetails.x_AHB))\r\n | extend x_SkuLicenseType = case(\r\n ChargeCategory != 'Usage', '',\r\n x_SkuMeterCategory in ('Virtual Machines', 'Virtual Machine Licenses') and (x_SkuMeterSubcategory contains 'Windows' or coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL'), 'Windows Server',\r\n isnotempty(tmp_SqlAhb) or x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\r\n ''\r\n )\r\n | extend x_SkuLicenseStatus = case(\r\n isempty(x_SkuLicenseType), '',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL' or tmp_SqlAhb == 'true' or x_SkuMeterSubcategory contains 'Azure Hybrid Benefit', 'Enabled',\r\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not Enabled',\r\n ''\r\n )\r\n | extend x_SkuLicenseQuantity = case(\r\n isempty(x_SkuCoreCount) or isempty(x_SkuLicenseType), int(null),\r\n x_SkuCoreCount <= 8, int(8),\r\n x_SkuCoreCount > 8, x_SkuCoreCount,\r\n int(null)\r\n )\r\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\r\n //\r\n // Savings\r\n | extend x_CommitmentDiscountSavings = iff(isempty(ContractedCost) or ContractedCost == 0 or ContractedCost - EffectiveCost < 0.0001, real(0), ContractedCost - EffectiveCost)\r\n | extend x_NegotiatedDiscountSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - ContractedCost < 0.0001, real(0), ListCost - ContractedCost)\r\n | extend x_TotalSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - EffectiveCost < 0.0001, real(0), ListCost - EffectiveCost)\r\n | extend x_CommitmentDiscountPercent = iff(isempty(ContractedUnitPrice) or ContractedUnitPrice == 0 or ContractedUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\r\n | extend x_NegotiatedDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - ContractedUnitPrice < 0.0001, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\r\n | extend x_TotalDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\r\n //\r\n // Minor fixes\r\n | extend old_BillingPeriodEnd = BillingPeriodEnd, BillingPeriodEnd = startofmonth(BillingPeriodEnd)\r\n | extend old_BillingPeriodStart = BillingPeriodStart, BillingPeriodStart = startofmonth(BillingPeriodStart)\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n CapacityReservationId,\r\n CapacityReservationStatus,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\r\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\r\n EffectiveCost,\r\n InvoiceId = coalesce(InvoiceId, x_InvoiceId),\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory, // TODO: Populate ServiceSubcategory from ServiceName when missing\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceDetails,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName = iff(isempty(SubAccountId), '', SubAccountName),\r\n SubAccountType,\r\n Tags,\r\n x_AccountId = iff(x_AccountId == '-2', '', x_AccountId),\r\n x_AccountName = iff(x_AccountId == '-2', '', x_AccountName),\r\n x_AccountOwnerId = iff(x_AccountId == '-2', '', x_AccountOwnerId),\r\n x_AmortizationClass,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\r\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\r\n ProviderName\r\n ),\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingItemCode,\r\n x_BillingItemName,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountPercent,\r\n x_CommitmentDiscountSavings,\r\n x_CommitmentDiscountSpendEligibility = '', // TODO: Add x_CommitmentDiscountSpendEligibility for Costs\r\n x_CommitmentDiscountUsageEligibility = '', // TODO: Add x_CommitmentDiscountUsageEligibility for Costs\r\n x_CommitmentDiscountUtilizationAmount,\r\n x_CommitmentDiscountUtilizationPotential,\r\n x_CommodityCode,\r\n x_CommodityName,\r\n x_ComponentName,\r\n x_ComponentType,\r\n x_ConsumedCoreHours,\r\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd),\r\n x_CostAllocationRuleName,\r\n x_CostCategories = parse_json(x_CostCategories),\r\n x_CostCenter,\r\n x_CostType,\r\n x_Credits = parse_json(x_Credits),\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount = parse_json(x_Discount),\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InstanceID,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId = case(\r\n x_InvoiceSectionId == '-2', '',\r\n x_InvoiceSectionId\r\n ),\r\n x_InvoiceSectionName = case(\r\n x_InvoiceSectionName == 'Unassigned', '',\r\n x_InvoiceSectionName\r\n ),\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_NegotiatedDiscountPercent,\r\n x_NegotiatedDiscountSavings,\r\n x_Operation,\r\n x_OwnerAccountID,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription = iff(x_PricingUnitDescription == 'Unassigned', '', x_PricingUnitDescription),\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName = tolower(x_ResourceGroupName),\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServiceModel, // TODO: Populate from ServiceName when missing\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuCoreCount,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuInstanceType,\r\n x_SkuIsCreditEligible,\r\n x_SkuLicenseQuantity,\r\n x_SkuLicenseStatus,\r\n x_SkuLicenseType,\r\n x_SkuLicenseUnit,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOperatingSystem,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuPlanName,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceValues = bag_merge(\r\n checkString('BillingPeriodEnd', old_BillingPeriodEnd, BillingPeriodEnd),\r\n checkString('BillingPeriodStart', old_BillingPeriodStart, BillingPeriodStart),\r\n checkString('CapacityReservationStatus', old_CapacityReservationStatus, CapacityReservationStatus),\r\n checkString('ChargeCategory', old_ChargeCategory, ChargeCategory),\r\n checkString('ChargeClass', old_ChargeClass, ChargeClass),\r\n checkString('ChargeSubcategory', old_ChargeSubcategory, ''), // Not included in final schema; use empty string\r\n checkString('ChargeFrequency', old_ChargeFrequency, ChargeFrequency),\r\n checkReal('CommitmentDiscountQuantity', old_CommitmentDiscountQuantity, CommitmentDiscountQuantity),\r\n checkString('CommitmentDiscountUnit', old_CommitmentDiscountUnit, CommitmentDiscountUnit),\r\n checkString('CommitmentDiscountStatus', old_CommitmentDiscountStatus, CommitmentDiscountStatus),\r\n checkReal('ConsumedQuantity', old_ConsumedQuantity, ConsumedQuantity),\r\n checkString('ConsumedUnit', old_ConsumedUnit, ConsumedUnit),\r\n checkReal('ContractedCost', old_ContractedCost, ContractedCost),\r\n checkReal('ContractedUnitPrice', old_ContractedUnitPrice, ContractedUnitPrice),\r\n checkReal('EffectiveCost', old_EffectiveCost, EffectiveCost),\r\n checkReal('ListCost', old_ListCost, ListCost),\r\n checkReal('ListUnitPrice', old_ListUnitPrice, ListUnitPrice),\r\n checkString('PricingCategory', old_PricingCategory, PricingCategory),\r\n checkReal('PricingQuantity', old_PricingQuantity, PricingQuantity),\r\n checkString('ProviderName', old_ProviderName, ProviderName),\r\n checkString('PublisherName', old_PublisherName, PublisherName),\r\n checkString('Region', old_Region, ''), // Not included in final schema; use empty string\r\n checkString('RegionId', old_RegionId, RegionId),\r\n checkString('ResourceId', old_ResourceId, ResourceId),\r\n checkString('ResourceName', old_ResourceName, ResourceName),\r\n checkString('ResourceType', old_ResourceType, ResourceType),\r\n checkString('x_AmortizationClass', old_x_AmortizationClass, x_AmortizationClass),\r\n checkReal('x_EffectiveCostInUsd', old_x_EffectiveCostInUsd, x_EffectiveCostInUsd),\r\n checkReal('x_EffectiveUnitPrice', old_x_EffectiveUnitPrice, x_EffectiveUnitPrice),\r\n checkString('x_ResourceType', old_x_ResourceType, x_ResourceType)\r\n ),\r\n x_SourceVersion,\r\n x_SubproductName,\r\n x_TotalDiscountPercent,\r\n x_TotalSavings,\r\n x_UsageType\r\n}\r\n\r\n// Costs_final_v1_2 table\r\n.create-merge table Costs_final_v1_2 (\r\n AvailabilityZone: string,\r\n BilledCost: real,\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingAccountType: string,\r\n BillingCurrency: string,\r\n BillingPeriodEnd: datetime,\r\n BillingPeriodStart: datetime,\r\n CapacityReservationId: string,\r\n CapacityReservationStatus: string,\r\n ChargeCategory: string,\r\n ChargeClass: string,\r\n ChargeDescription: string,\r\n ChargeFrequency: string,\r\n ChargePeriodEnd: datetime,\r\n ChargePeriodStart: datetime,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountId: string,\r\n CommitmentDiscountName: string,\r\n CommitmentDiscountQuantity: real,\r\n CommitmentDiscountStatus: string,\r\n CommitmentDiscountType: string,\r\n CommitmentDiscountUnit: string,\r\n ConsumedQuantity: real,\r\n ConsumedUnit: string,\r\n ContractedCost: real,\r\n ContractedUnitPrice: real,\r\n EffectiveCost: real,\r\n InvoiceId: string,\r\n InvoiceIssuerName: string,\r\n ListCost: real,\r\n ListUnitPrice: real,\r\n PricingCategory: string,\r\n PricingCurrency: string,\r\n PricingQuantity: real,\r\n PricingUnit: string,\r\n ProviderName: string,\r\n PublisherName: string,\r\n RegionId: string,\r\n RegionName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n ServiceCategory: string,\r\n ServiceName: string,\r\n ServiceSubcategory: string,\r\n SkuId: string,\r\n SkuMeter: string,\r\n SkuPriceDetails: dynamic,\r\n SkuPriceId: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n SubAccountType: string,\r\n Tags: dynamic,\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_AmortizationClass: string, // Azure 1.2-preview+\r\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingItemCode: string, // Alibaba 1.0\r\n x_BillingItemName: string, // Alibaba 1.0\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_CommitmentDiscountNormalizedRatio: real, // Azure 1.2-preview+\r\n x_CommitmentDiscountPercent: real, // Hubs add-on\r\n x_CommitmentDiscountSavings: real, // Hubs add-on\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUtilizationAmount: real, // Hubs add-on\r\n x_CommitmentDiscountUtilizationPotential: real, // Hubs add-on\r\n x_CommodityCode: string, // Alibaba 1.0\r\n x_CommodityName: string, // Alibaba 1.0\r\n x_ComponentName: string, // Tencent 1.0\r\n x_ComponentType: string, // Tencent 1.0\r\n x_ConsumedCoreHours: real, // Hubs add-on\r\n x_ContractedCostInUsd: real, // Azure 1.0+\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_CostType: string, // GCP Jan 2024\r\n x_Credits: dynamic, // GCP Jan 2024\r\n x_CurrencyConversionRate: real, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: dynamic, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_InstanceID: string, // Alibaba 1.0\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_NegotiatedDiscountPercent:real, // Hubs add-on\r\n x_NegotiatedDiscountSavings:real, // Hubs add-on\r\n x_Operation: string, // AWS 1.0\r\n x_OwnerAccountID: string, // Tencent 1.0\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServiceModel: string, // Azure 1.2-preview+\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuCoreCount: int, // Hubs add-on\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\r\n x_SkuInstanceType: string, // Hubs add-on\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuLicenseQuantity: int, // Hubs add-on\r\n x_SkuLicenseStatus: string, // Hubs add-on\r\n x_SkuLicenseType: string, // Hubs add-on\r\n x_SkuLicenseUnit: string, // Hubs add-on\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOperatingSystem: string, // Hubs add-on\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuPlanName: string, // Azure 1.2-preview+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceChanges: string, // Hubs add-on\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceValues: dynamic, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubproductName: string, // Tencent 1.0\r\n x_TotalDiscountPercent: real, // Hubs add-on\r\n x_TotalSavings: real, // Hubs add-on\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Update policy for Costs_raw -> Costs_final_v1_2 table\r\n.alter table Costs_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Costs_raw\",\r\n \"Query\": \"Costs_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Actual costs |===================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\r\nActualCosts_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n ActualCosts_raw\r\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodEnd = Date + 1d,\r\n ChargePeriodStart = Date,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId = '',\r\n SkuMeter = MeterName,\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentType = '',\r\n x_ComponentName = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = '',\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel,\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for ActualCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"ActualCosts_raw\",\r\n \"Query\": \"ActualCosts_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Amortized costs |================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\r\nAmortizedCosts_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n AmortizedCosts_raw\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodEnd = Date + 1d,\r\n ChargePeriodStart = Date,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId = '',\r\n SkuMeter = MeterName,\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentType = '',\r\n x_ComponentName = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = '',\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel,\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for AmortizedCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"AmortizedCosts_raw\",\r\n \"Query\": \"AmortizedCosts_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All commitment discount usage transformed to FOCUS 1.2. This includes reservationdeatils_raw.', folder='Commitment discounts')\r\nCommitmentDiscountUsage_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n CommitmentDiscountUsage_raw\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Handle resource columns\r\n | extend ResourceId = tolower(InstanceId)\r\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\r\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\r\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\r\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\r\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\r\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n ChargePeriodEnd = UsageDate + 1d,\r\n ChargePeriodStart = UsageDate,\r\n CommitmentDiscountCategory = 'Usage',\r\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\r\n CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\r\n CommitmentDiscountType = 'Reservation',\r\n CommitmentDiscountUnit = case(\r\n InstanceFlexibilityRatio == 1, 'Hours',\r\n InstanceFlexibilityRatio != 1, 'Normalized Hours',\r\n ''\r\n ),\r\n ConsumedQuantity = UsedHours,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\r\n x_CommitmentDiscountCommittedAmount = ReservedHours,\r\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\r\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\r\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\r\n x_IngestionTime = ingestion_time(),\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n // x_RowId = hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // CommitmentDiscountId,\r\n // ResourceId,\r\n // ChargePeriodStart\r\n // )),\r\n x_ServiceModel,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\r\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\r\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\r\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\r\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\r\n}\r\n\r\n// CommitmentDiscountUsage_final_v1_2 table\r\n.create-merge table CommitmentDiscountUsage_final_v1_2 (\r\n ChargePeriodEnd: datetime, // Hubs add-on\r\n ChargePeriodStart: datetime, // MS 2023-03-01\r\n CommitmentDiscountCategory: string, // Hubs add-on\r\n CommitmentDiscountId: string, // MS 2023-03-01\r\n CommitmentDiscountQuantity: real, // MS 2023-03-01\r\n CommitmentDiscountType: string, // Hubs add-on\r\n CommitmentDiscountUnit: string, // Hubs add-on\r\n ConsumedQuantity: real, // MS 2023-03-01\r\n ProviderName: string, // Hubs add-on\r\n ResourceId: string, // MS 2023-03-01\r\n ResourceName: string, // Hubs add-on\r\n ResourceType: string, // Hubs add-on\r\n ServiceCategory: string, // Hubs add-on\r\n ServiceName: string, // Hubs add-on\r\n ServiceSubcategory: string, // Hubs add-on\r\n SubAccountId: string, // Hubs add-on\r\n x_CommitmentDiscountCommittedCount: real, // MS 2023-03-01\r\n x_CommitmentDiscountCommittedAmount: real, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedRatio: real, // MS 2023-03-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_ResourceGroupName: string, // Hubs add-on\r\n x_ResourceType: string, // Hubs add-on\r\n x_ServiceModel: string, // Hubs add-on\r\n x_SkuOrderId: string, // MS 2023-03-01\r\n x_SkuSize: string, // MS 2023-03-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string // Hubs add-on\r\n)\r\n\r\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_2 table\r\n.alter table CommitmentDiscountUsage_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"CommitmentDiscountUsage_raw\",\r\n \"Query\": \"CommitmentDiscountUsage_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All recommendations transformed to FOCUS 1.2.', folder='Recommendations')\r\nRecommendations_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Recommendations_raw\r\n | extend x_IngestionTime = ingestion_time()\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Convert JSON cost columns to real\r\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\r\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\r\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\r\n //\r\n // Build recommendation details\r\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\r\n | extend x_RecommendationDetails = case(\r\n // Use incoming x_RecommendationDetails first\r\n isnotempty(x_RecommendationDetails), x_RecommendationDetails,\r\n // Create one for reservation recommendations if needed\r\n x_SourceType == 'ReservationRecommendations', bag_pack(\r\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\r\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\r\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\r\n 'CommitmentDiscountResourceType', ResourceType,\r\n 'CommitmentDiscountScope', Scope,\r\n 'LookbackPeriodDuration', case(\r\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\r\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\r\n ''\r\n ),\r\n 'LookbackPeriodStart', FirstUsageDate,\r\n 'RecommendedQuantity', RecommendedQuantity,\r\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\r\n 'RegionId', Location,\r\n 'RegionName', RegionName,\r\n 'SkuMeterId', MeterId,\r\n 'SkuPriceDetails', SkuProperties,\r\n 'SkuSize', coalesce(SKU, SkuName),\r\n 'SkuTerm', isoMonths(Term)\r\n ),\r\n dynamic({})\r\n )\r\n //\r\n // Prefer specified date, then fall back to generating a date based on reservation recommendation lookback period, then validate to ensure it's not in the future\r\n | extend x_RecommendationDate = coalesce(x_RecommendationDate, FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d))\r\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\r\n //\r\n | project\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n SubAccountId = coalesce(SubAccountId, iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), '')),\r\n SubAccountName,\r\n x_EffectiveCostAfter = coalesce(x_EffectiveCostAfter, TotalCostWithReservedInstances),\r\n x_EffectiveCostBefore = coalesce(x_EffectiveCostBefore, CostWithNoReservedInstances),\r\n x_EffectiveCostSavings = coalesce(x_EffectiveCostSavings, NetSavings),\r\n x_IngestionTime,\r\n x_RecommendationCategory, // TODO: Set for reservation recommendations\r\n x_RecommendationDate,\r\n x_RecommendationDescription,\r\n x_RecommendationDetails,\r\n x_RecommendationId, // TODO: Set for reservation recommendations\r\n x_ResourceGroupName,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n// Recommendations_final_v1_2 table\r\n.create-merge table Recommendations_final_v1_2 (\r\n ProviderName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n x_EffectiveCostAfter: real,\r\n x_EffectiveCostBefore: real,\r\n x_EffectiveCostSavings: real,\r\n x_IngestionTime: datetime,\r\n x_RecommendationCategory: string,\r\n x_RecommendationDate: datetime,\r\n x_RecommendationDescription: string,\r\n x_RecommendationDetails: dynamic,\r\n x_RecommendationId: string,\r\n x_ResourceGroupName: string,\r\n x_SourceName: string,\r\n x_SourceProvider: string,\r\n x_SourceType: string,\r\n x_SourceVersion: string\r\n)\r\n\r\n// Update policy for Recommendations_raw -> Recommendations_final_v1_2 table\r\n.alter table Recommendations_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Recommendations_raw\",\r\n \"Query\": \"Recommendations_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All transactions transformed to FOCUS 1.2.', folder='Transactions')\r\nTransactions_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Transactions_raw\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Handle BillingPeriodStart/End\r\n | extend BillingMonth = tostring(BillingMonth)\r\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\r\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n BilledCost = Amount,\r\n BillingAccountId = case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\r\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\r\n ''\r\n ),\r\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\r\n BillingCurrency = Currency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory = case(\r\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = case(\r\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\r\n EventType == 'Refund', 'Correction',\r\n ''\r\n ),\r\n ChargeDescription = Description,\r\n ChargeFrequency = case(\r\n BillingFrequency == 'OneTime', 'One-Time',\r\n BillingFrequency == 'Recurring', 'Recurring',\r\n BillingFrequency\r\n ),\r\n ChargePeriodStart = EventDate,\r\n InvoiceId,\r\n PricingQuantity = Quantity,\r\n PricingUnit = 'Reservations',\r\n ProviderName,\r\n RegionId = Region,\r\n RegionName = Region,\r\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\r\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerEmail,\r\n x_CostCenter = CostCenter,\r\n x_InvoiceNumber = Invoice,\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\r\n x_IngestionTime = ingestion_time(),\r\n x_MonetaryCommitment = MonetaryCommitment,\r\n x_Overage = Overage,\r\n x_PurchasingBillingAccountId = PurchasingEnrollment,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuOrderName = ReservationOrderName,\r\n x_SkuSize = ArmSkuName,\r\n x_SkuTerm = isoMonths(Term),\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId = PurchasingSubscriptionGuid,\r\n x_TransactionType = EventType\r\n}\r\n\r\n// Transactions_final_v1_2 table\r\n.create-merge table Transactions_final_v1_2 (\r\n BilledCost: real, // MS CM EA+MCA 2023-05-01\r\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\r\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\r\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n ChargeCategory: string, // Hubs add-on\r\n ChargeClass: string, // Hubs add-on\r\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\r\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\r\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n InvoiceId: string, // MS CM MCA 2023-05-01\r\n PricingQuantity: real, // MS CM EA+MCA 2023-05-01\r\n PricingUnit: string, // Hubs add-on\r\n ProviderName: string, // Hubs add-on\r\n RegionId: string, // MS CM EA+MCA 2023-05-01\r\n RegionName: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\r\n x_AccountName: string, // MS CM EA 2023-05-01\r\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\r\n x_CostCenter: string, // MS CM EA 2023-05-01\r\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_MonetaryCommitment: real, // MS CM EA 2023-05-01\r\n x_Overage: real, // MS CM EA 2023-05-01\r\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\r\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\r\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\r\n)\r\n\r\n// Update policy for Transactions_raw -> Transactions_final_v1_2 table\r\n.alter table Transactions_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Transactions_raw\",\r\n \"Query\": \"Transactions_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n", - "$fxv#11": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Common utility functions\r\n//\r\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\r\n//======================================================================================================================\r\n\r\n\r\n//===| Date functions |=================================================================================================\r\n\r\n// monthstring\r\n.create-or-alter function \r\nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \r\nmonthstring(['date']: datetime, length: int = 9)\r\n{\r\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\r\n}\r\n\r\n// datestring\r\n.create-or-alter function \r\nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \r\ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n let month = (d: datetime) { monthstring(d, 3) };\r\n let endDate = iff(end == datetime('0001-01-01'), start, end);\r\n let sameDate = startofday(start) == startofday(endDate);\r\n let sameMonth = startofmonth(start) == startofmonth(endDate);\r\n let sameYear = startofyear(start) == startofyear(endDate);\r\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\r\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\r\n let currentYear = sameYear and startofyear(start) == startofyear(now());\r\n case(\r\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\r\n fullYear,\r\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\r\n // 1 full mo, same year | Mmm yyyy\r\n fullMonth and sameMonth and sameYear,\r\n strcat(month(start), ' ', getyear(start)),\r\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\r\n fullMonth and sameYear,\r\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\r\n fullMonth and not(sameYear),\r\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\r\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\r\n sameDate,\r\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\r\n not(fullMonth) and sameMonth and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\r\n not(fullMonth) and not(sameMonth) and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\r\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\r\n )\r\n}\r\n\r\n// daterange\r\n.create-or-alter function \r\nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \r\ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n datestring(start, end)\r\n}\r\n\r\n// monthsago\r\n.create-or-alter function \r\nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\r\nmonthsago(months: int)\r\n{\r\n datetime_add('month', -months, startofmonth(now()))\r\n}\r\n\r\n\r\n//===| Number functions |===============================================================================================\r\n// NOTE: Must be defined before string converters\r\n\r\n// delta\r\n.create-or-alter function \r\nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \r\ndelta(oldval: double, newval: double)\r\n{\r\n (newval - todouble(oldval))/oldval\r\n}\r\n\r\n// percentOfTotal\r\n// NOTE: Must be before percent() function\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercentOfTotal(t: (Count: long), tot: long)\r\n{\r\n let total = todouble(tot);\r\n t \r\n | extend Percent = round(Count / total * 100, 3) \r\n | order by Count desc\r\n}\r\n\r\n// percent\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercent(t: (Count: long))\r\n{\r\n let total = todouble(toscalar(t | summarize sum(Count)));\r\n percentOfTotal(t, total)\r\n}\r\n\r\n// plusminus\r\n.create-or-alter function \r\nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\r\nplusminus(val: string)\r\n{\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, val, strcat('+', val))\r\n}\r\n\r\n// updown\r\n.create-or-alter function \r\nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\r\nupdown(val: string)\r\n{\r\n // TODO: Handle 0\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\r\n}\r\n\r\n\r\n//===| String functions |===============================================================================================\r\n\r\n// percentstring\r\n// NOTE: Must be defined before deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\r\npercentstring(num: double, total: double = 1.0, places: int = 9)\r\n{\r\n let value = 1.0 * num / total * 100;\r\n strcat(case(\r\n places != 9, round(value, places),\r\n value < 10, round(value, 2),\r\n round(value, 1)\r\n ), '%')\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// arraystring\r\n.create-or-alter function \r\nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\r\narraystring(arr: dynamic)\r\n{\r\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\r\n tostring(arr)\r\n , @'^\\[\"', '')\r\n , @'\"\\]$', '')\r\n , @'^, ', '')\r\n , @', $', '')\r\n , @'^\\[]$', '')\r\n , '\",\"', ', ')\r\n}\r\n\r\n// deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\r\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\r\n{\r\n let d = delta(oldval, newval);\r\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\r\n}\r\n\r\n// diffstring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\r\ndiffstring(oldval: double, newval: double, places: int = 1)\r\n{\r\n plusminus(round(newval - oldval, places))\r\n}\r\n\r\n// numberstring\r\n.create-or-alter function \r\nwith (docstring = 'Convert a number to a string', folder = 'Common')\r\nnumberstring(num: double, abbrev: bool = true)\r\n{\r\n replace_regex(case(\r\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\r\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\r\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\r\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\r\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\r\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\r\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\r\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\r\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\r\n tostring(num)\r\n ), @'\\.0$', '')\r\n}\r\n\r\n\r\n//===| Other |==========================================================================================================\r\n\r\n// ifempty\r\n.create-or-alter function \r\nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\r\nifempty(val: dynamic, defaultVal: dynamic)\r\n{\r\n iff(isempty(val), defaultVal, val)\r\n}\r\n", - "$fxv#12": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / Open data functions\r\n// Wrap Ingestion database tables for easy access.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// PricingUnits\r\n.create-or-alter function\r\nwith (docstring = 'Gets pricing units from the FinOps toolkit PricingUnits open data.', folder = 'OpenData')\r\nPricingUnits()\r\n{\r\n database('Ingestion').PricingUnits\r\n}\r\n\r\n// Regions\r\n.create-or-alter function\r\nwith (docstring = 'Gets regions from the FinOps toolkit Regions open data.', folder = 'OpenData')\r\nRegion()\r\n{\r\n database('Ingestion').Regions\r\n}\r\n\r\n// ResourceTypes\r\n.create-or-alter function\r\nwith (docstring = 'Gets resource types from the FinOps toolkit ResourceTypes open data.', folder = 'OpenData')\r\nResourceType()\r\n{\r\n database('Ingestion').ResourceTypes\r\n}\r\n\r\n// Services\r\n.create-or-alter function\r\nwith (docstring = 'Gets services from the FinOps toolkit Services open data.', folder = 'OpenData')\r\nServices()\r\n{\r\n database('Ingestion').Services\r\n}\r\n", - "$fxv#13": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / FOCUS 1.0 functions\r\n// Used for reporting with backward compatibility.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// CommitmentDiscountUsage_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.0.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage_v1_0()\r\n{\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\r\n | union (\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n x_CommitmentDiscountCommittedCount = todecimal(x_CommitmentDiscountCommittedCount),\r\n x_CommitmentDiscountCommittedAmount = todecimal(x_CommitmentDiscountCommittedAmount),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio)\r\n )\r\n | project\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount,\r\n x_CommitmentDiscountCommittedAmount,\r\n x_CommitmentDiscountNormalizedGroup,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountQuantity,\r\n x_IngestionTime,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceModel,\r\n x_SkuOrderId,\r\n x_SkuSize,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Costs_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.0.', folder = 'Costs')\r\nCosts_v1_0()\r\n{\r\n database('Ingestion').Costs_final_v1_0\r\n | union (\r\n database('Ingestion').Costs_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n ContractedCost = todecimal(ContractedCost),\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n EffectiveCost = todecimal(EffectiveCost),\r\n ListCost = todecimal(ListCost),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\r\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\r\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\r\n // Rename columns\r\n | project-rename\r\n x_InvoiceId = InvoiceId,\r\n x_PricingCurrency = PricingCurrency,\r\n x_SkuMeterName = SkuMeter\r\n // Generate historical x_SkuDetails format from SkuPriceDetails\r\n | extend x_SkuDetails = iff(isnotempty(x_SkuDetails), x_SkuDetails, parse_json(replace_regex(tostring(SkuPriceDetails), @'([\\{,])\"x_', @'\\1\"')))\r\n )\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost,\r\n ContractedUnitPrice,\r\n EffectiveCost,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SkuId,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType,\r\n Tags,\r\n x_AccountId,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_ContractedCostInUsd,\r\n x_CostAllocationRuleName,\r\n x_CostCategories,\r\n x_CostCenter,\r\n x_Credits,\r\n x_CostType,\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount,\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InvoiceId,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_Operation,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingCurrency,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuIsCreditEligible,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_UsageType\r\n}\r\n\r\n\r\n// Prices_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices aligned to FOCUS 1.0.', folder = 'Prices')\r\nPrices_v1_0()\r\n{\r\n database('Ingestion').Prices_final_v1_0\r\n | union (\r\n database('Ingestion').Prices_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n x_BaseUnitPrice = todecimal(x_BaseUnitPrice),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\r\n x_ContractedUnitPriceDiscount = todecimal(x_ContractedUnitPriceDiscount),\r\n x_ContractedUnitPriceDiscountPercent = todecimal(x_ContractedUnitPriceDiscountPercent),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_EffectiveUnitPriceDiscount = todecimal(x_EffectiveUnitPriceDiscount),\r\n x_EffectiveUnitPriceDiscountPercent = todecimal(x_EffectiveUnitPriceDiscountPercent),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize),\r\n x_SkuIncludedQuantity = todecimal(x_SkuIncludedQuantity),\r\n x_SkuTier = todecimal(x_SkuTier),\r\n x_TotalUnitPriceDiscount = todecimal(x_TotalUnitPriceDiscount),\r\n x_TotalUnitPriceDiscountPercent = todecimal(x_TotalUnitPriceDiscountPercent) \r\n // Rename columns\r\n | project-rename\r\n x_PricingCurrency = PricingCurrency,\r\n x_SkuMeterName = SkuMeter\r\n )\r\n | project\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType,\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingUnit,\r\n SkuId,\r\n SkuPriceId,\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent,\r\n x_EffectivePeriodEnd,\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingCurrency,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_SkuDescription,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent\r\n}\r\n\r\n\r\n// Recommendations_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.0.', folder = 'Recommendations')\r\nRecommendations_v1_0()\r\n{\r\n database('Ingestion').Recommendations_final_v1_0\r\n | union (\r\n database('Ingestion').Recommendations_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n x_EffectiveCostAfter = todecimal(x_EffectiveCostAfter),\r\n x_EffectiveCostBefore = todecimal(x_EffectiveCostBefore),\r\n x_EffectiveCostSavings = todecimal(x_EffectiveCostSavings)\r\n )\r\n | project\r\n ProviderName,\r\n SubAccountId,\r\n x_IngestionTime,\r\n x_EffectiveCostAfter,\r\n x_EffectiveCostBefore,\r\n x_EffectiveCostSavings,\r\n x_RecommendationDate,\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Transactions_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.0.', folder = 'Transactions')\r\nTransactions_v1_0()\r\n{\r\n database('Ingestion').Transactions_final_v1_0\r\n | union (\r\n database('Ingestion').Transactions_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n x_MonetaryCommitment = todecimal(x_MonetaryCommitment),\r\n x_Overage = todecimal(x_Overage)\r\n // Rename columns\r\n | project-rename\r\n x_InvoiceId = InvoiceId\r\n )\r\n | project\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodStart,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n RegionId,\r\n RegionName,\r\n SubAccountId,\r\n SubAccountName,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_CostCenter,\r\n x_InvoiceId,\r\n x_InvoiceNumber,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_IngestionTime,\r\n x_MonetaryCommitment,\r\n x_Overage,\r\n x_PurchasingBillingAccountId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuSize,\r\n x_SkuTerm,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId,\r\n x_TransactionType\r\n}\r\n", - "$fxv#14": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / FOCUS 1.2 functions\r\n// Used for reporting with backward compatibility.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// CommitmentDiscountUsage_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.2.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage_v1_2()\r\n{\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\r\n | union (\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n ConsumedQuantity = toreal(ConsumedQuantity),\r\n x_CommitmentDiscountCommittedCount = toreal(x_CommitmentDiscountCommittedCount),\r\n x_CommitmentDiscountCommittedAmount = toreal(x_CommitmentDiscountCommittedAmount),\r\n x_CommitmentDiscountNormalizedRatio = toreal(x_CommitmentDiscountNormalizedRatio)\r\n // Add new columns\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceSubcategory) on x_ResourceType\r\n | extend CommitmentDiscountQuantity = ConsumedQuantity * x_CommitmentDiscountNormalizedRatio\r\n | extend CommitmentDiscountUnit = case(\r\n x_CommitmentDiscountNormalizedRatio == 1, 'Hours',\r\n x_CommitmentDiscountNormalizedRatio > 1, 'Normalized Hours',\r\n ''\r\n )\r\n )\r\n | project\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount,\r\n x_CommitmentDiscountCommittedAmount,\r\n x_CommitmentDiscountNormalizedGroup,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_IngestionTime,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceModel,\r\n x_SkuOrderId,\r\n x_SkuSize,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Costs_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.2.', folder = 'Costs')\r\nCosts_v1_2()\r\n{\r\n database('Ingestion').Costs_final_v1_2\r\n | union (\r\n database('Ingestion').Costs_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n BilledCost = toreal(BilledCost),\r\n ConsumedQuantity = toreal(ConsumedQuantity),\r\n ContractedCost = toreal(ContractedCost),\r\n ContractedUnitPrice = toreal(ContractedUnitPrice),\r\n EffectiveCost = toreal(EffectiveCost),\r\n ListCost = toreal(ListCost),\r\n ListUnitPrice = toreal(ListUnitPrice),\r\n PricingQuantity = toreal(PricingQuantity),\r\n x_BilledCostInUsd = toreal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = toreal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = toreal(x_BillingExchangeRate),\r\n x_ContractedCostInUsd = toreal(x_ContractedCostInUsd),\r\n x_CurrencyConversionRate = toreal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = toreal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = toreal(x_ListCostInUsd),\r\n x_PricingBlockSize = toreal(x_PricingBlockSize)\r\n // Rename columns\r\n | project-rename\r\n InvoiceId = x_InvoiceId,\r\n PricingCurrency = x_PricingCurrency,\r\n SkuMeter = x_SkuMeterName\r\n // Add new columns\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | extend CapacityReservationId = tostring(x_SkuDetails.VMCapacityReservationId)\r\n | extend CapacityReservationStatus = case(\r\n isempty(CapacityReservationId), '',\r\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\r\n 'Used'\r\n )\r\n | extend x_CommitmentDiscountNormalizedRatio = case(\r\n // Not applicable\r\n isempty(CommitmentDiscountStatus), real(null),\r\n // Parse from SKU details if not specified explicitly\r\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, dynamic(1)))\r\n )\r\n | extend CommitmentDiscountQuantity = case(\r\n isempty(CommitmentDiscountStatus), real(null),\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\r\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\r\n real(null)\r\n )\r\n | extend CommitmentDiscountUnit = case(\r\n isempty(CommitmentDiscountQuantity), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\r\n ''\r\n )\r\n | extend x_AmortizationClass = case(\r\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\r\n ''\r\n )\r\n // Hubs add-ons\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n | extend x_SkuCoreCount = toint(coalesce(x_SkuDetails.VCPUs, x_SkuDetails.VCores, x_SkuDetails.vCores))\r\n | extend x_SkuInstanceType = tostring(coalesce(x_SkuDetails.ServiceType, x_SkuDetails.ServerSku))\r\n | extend x_SkuOperatingSystem = case(\r\n x_SkuDetails.ImageType == 'Canonical', 'Linux',\r\n x_SkuDetails.ImageType == 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\r\n x_SkuDetails.ImageType\r\n )\r\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\r\n | extend tmp_SqlAhb = tolower(x_SkuDetails.AHB)\r\n | extend x_SkuLicenseType = case(\r\n x_SkuDetails.ImageType contains 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\r\n ''\r\n )\r\n | extend x_SkuLicenseStatus = case(\r\n isnotempty(x_SkuLicenseType) or tmp_SqlAhb == 'true' or (x_SkuMeterSubcategory contains 'Azure Hybrid Benefit'), 'Enabled',\r\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not enabled',\r\n ''\r\n )\r\n | extend x_SkuLicenseQuantity = case(\r\n isempty(x_SkuCoreCount), int(null),\r\n x_SkuCoreCount <= 8, int(8),\r\n x_SkuCoreCount > 8, x_SkuCoreCount,\r\n int(null)\r\n )\r\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\r\n | extend x_CommitmentDiscountSavings = iff(ContractedCost < EffectiveCost, real(0), ContractedCost - EffectiveCost)\r\n | extend x_NegotiatedDiscountSavings = iff(ListCost < ContractedCost, real(0), ListCost - ContractedCost)\r\n | extend x_TotalSavings = iff(ListCost < EffectiveCost, real(0), ListCost - EffectiveCost)\r\n | extend x_CommitmentDiscountPercent = iff(ContractedUnitPrice == 0, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\r\n | extend x_NegotiatedDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\r\n | extend x_TotalDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\r\n // SkuPriceDetails conversion -- Must be after hubs add-ons\r\n | extend SkuPriceDetails = parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\r\n // Prefix all keys with x_ first to avoid double-prefixing\r\n , @'([\\{,])\"', @'\\1\"x_')\r\n // CoreCount for number of CPUs/vCPUs/cores/vCores\r\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\r\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\r\n // TODO: DiskSpace for disk size in GiB\r\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\r\n // TODO: GpuCount for the number of GPUs\r\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\r\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\r\n // TODO: InstanceSeries for the size family/series\r\n // TODO: MemorySize for the RAM in GiB\r\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\r\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\r\n // OperatingSystem for the OS name\r\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\r\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\r\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\r\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\r\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\r\n )\r\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\r\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\r\n SkuPriceDetails)\r\n )\r\n | extend SkuPriceDetails = iff(isnotempty(SkuPriceDetails), SkuPriceDetails, parse_json(replace_regex(tostring(x_SkuDetails), @'([\\{,])\"', @'\\1\"x_')))\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n CapacityReservationId,\r\n CapacityReservationStatus,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost,\r\n ContractedUnitPrice,\r\n EffectiveCost,\r\n InvoiceId,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceDetails,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType,\r\n Tags,\r\n x_AccountId,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_AmortizationClass,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingItemCode,\r\n x_BillingItemName,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountPercent,\r\n x_CommitmentDiscountSavings,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_CommitmentDiscountUtilizationAmount,\r\n x_CommitmentDiscountUtilizationPotential,\r\n x_CommodityCode,\r\n x_CommodityName,\r\n x_ComponentName,\r\n x_ComponentType,\r\n x_ConsumedCoreHours,\r\n x_ContractedCostInUsd,\r\n x_CostAllocationRuleName,\r\n x_CostCategories,\r\n x_CostCenter,\r\n x_CostType,\r\n x_Credits,\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount,\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InstanceID,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_NegotiatedDiscountPercent,\r\n x_NegotiatedDiscountSavings,\r\n x_Operation,\r\n x_OwnerAccountID,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServiceModel,\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuCoreCount,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuInstanceType,\r\n x_SkuIsCreditEligible,\r\n x_SkuLicenseQuantity,\r\n x_SkuLicenseStatus,\r\n x_SkuLicenseType,\r\n x_SkuLicenseUnit,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOperatingSystem,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuPlanName,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceValues,\r\n x_SourceVersion,\r\n x_SubproductName,\r\n x_TotalDiscountPercent,\r\n x_TotalSavings,\r\n x_UsageType\r\n}\r\n\r\n\r\n// Prices_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices aligned to FOCUS 1.2.', folder = 'Prices')\r\nPrices_v1_2()\r\n{\r\n database('Ingestion').Prices_final_v1_2\r\n | union (\r\n database('Ingestion').Prices_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n ContractedUnitPrice = toreal(ContractedUnitPrice),\r\n ListUnitPrice = toreal(ListUnitPrice),\r\n x_BaseUnitPrice = toreal(x_BaseUnitPrice),\r\n x_ContractedUnitPriceDiscount = toreal(x_ContractedUnitPriceDiscount),\r\n x_ContractedUnitPriceDiscountPercent = toreal(x_ContractedUnitPriceDiscountPercent),\r\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\r\n x_EffectiveUnitPriceDiscount = toreal(x_EffectiveUnitPriceDiscount),\r\n x_EffectiveUnitPriceDiscountPercent = toreal(x_EffectiveUnitPriceDiscountPercent),\r\n x_PricingBlockSize = toreal(x_PricingBlockSize),\r\n x_SkuIncludedQuantity = toreal(x_SkuIncludedQuantity),\r\n x_SkuTier = toreal(x_SkuTier),\r\n x_TotalUnitPriceDiscount = toreal(x_TotalUnitPriceDiscount),\r\n x_TotalUnitPriceDiscountPercent = toreal(x_TotalUnitPriceDiscountPercent) \r\n // Rename columns\r\n | project-rename\r\n PricingCurrency = x_PricingCurrency,\r\n SkuMeter = x_SkuMeterName\r\n )\r\n | project\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingUnit,\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceId,\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent,\r\n x_EffectivePeriodEnd,\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_SkuDescription,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent\r\n}\r\n\r\n\r\n// Recommendations_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.2.', folder = 'Recommendations')\r\nRecommendations_v1_2()\r\n{\r\n database('Ingestion').Recommendations_final_v1_2\r\n | union (\r\n database('Ingestion').Recommendations_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n x_EffectiveCostAfter = toreal(x_EffectiveCostAfter),\r\n x_EffectiveCostBefore = toreal(x_EffectiveCostBefore),\r\n x_EffectiveCostSavings = toreal(x_EffectiveCostSavings)\r\n )\r\n | project\r\n ProviderName,\r\n SubAccountId,\r\n x_IngestionTime,\r\n x_EffectiveCostAfter,\r\n x_EffectiveCostBefore,\r\n x_EffectiveCostSavings,\r\n x_RecommendationDate,\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Transactions_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.2.', folder = 'Transactions')\r\nTransactions_v1_2()\r\n{\r\n database('Ingestion').Transactions_final_v1_2\r\n | union (\r\n database('Ingestion').Transactions_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n BilledCost = toreal(BilledCost),\r\n PricingQuantity = toreal(PricingQuantity),\r\n x_MonetaryCommitment = toreal(x_MonetaryCommitment),\r\n x_Overage = toreal(x_Overage)\r\n // Rename columns\r\n | project-rename\r\n InvoiceId = x_InvoiceId\r\n )\r\n | project\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodStart,\r\n InvoiceId,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n RegionId,\r\n RegionName,\r\n SubAccountId,\r\n SubAccountName,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_CostCenter,\r\n x_InvoiceNumber,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_IngestionTime,\r\n x_MonetaryCommitment,\r\n x_Overage,\r\n x_PurchasingBillingAccountId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuSize,\r\n x_SkuTerm,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId,\r\n x_TransactionType\r\n}\r\n\r\n\r\n//======================================================================================================================\r\n// Latest FOCUS version\r\n//======================================================================================================================\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage()\r\n{\r\n CommitmentDiscountUsage_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\r\nCosts()\r\n{\r\n Costs_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\r\nPrices()\r\n{\r\n Prices_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\r\nRecommendations()\r\n{\r\n Recommendations_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\r\nTransactions()\r\n{\r\n Transactions_v1_2()\r\n}\r\n", - "$fxv#15": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / Latest FOCUS version functions\r\n// Used for ad hoc queries.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage()\r\n{\r\n CommitmentDiscountUsage_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\r\nCosts()\r\n{\r\n Costs_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\r\nPrices()\r\n{\r\n Prices_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\r\nRecommendations()\r\n{\r\n Recommendations_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\r\nTransactions()\r\n{\r\n Transactions_v1_2()\r\n}\r\n", - "$fxv#2": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_3(id: string) {\r\n dynamic({\r\n \"microsoft.hybridnetwork/vendors\": { \"SingularDisplayName\": \"Azure Network Function Manager ? vendor\" }\r\n ,\"microsoft.hybridonboarding/extensionmanagers\": { \"SingularDisplayName\": \"Microsoft.HybridOnboarding extension manager\" }\r\n ,\"microsoft.impact/connectors\": { \"SingularDisplayName\": \"Impact Reporting Connector\" }\r\n ,\"microsoft.impact/impactcategories\": { \"SingularDisplayName\": \"Microsoft.Impact impact category\" }\r\n ,\"microsoft.impact/topologyimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact topology impact\" }\r\n ,\"microsoft.impact/workloadimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact workload impact\" }\r\n ,\"microsoft.impact/workloadimpacts/insights\": { \"SingularDisplayName\": \"Microsoft.Impact workload impacts insight\" }\r\n ,\"microsoft.importexport/jobs\": { \"SingularDisplayName\": \"Microsoft.ImportExport job\" }\r\n ,\"microsoft.insights/actiongroups\": { \"SingularDisplayName\": \"Action group\" }\r\n ,\"microsoft.insights/activitylogalerts\": { \"SingularDisplayName\": \"Activity log alert rule\" }\r\n ,\"microsoft.insights/alertrules\": { \"SingularDisplayName\": \"Microsoft.Insights alertrule\" }\r\n ,\"microsoft.insights/alertrules/incidents\": { \"SingularDisplayName\": \"Microsoft.insights alertrules incident\" }\r\n ,\"microsoft.insights/autoscalesettings\": { \"SingularDisplayName\": \"Microsoft.Insights autoscalesetting\" }\r\n ,\"microsoft.insights/components\": { \"SingularDisplayName\": \"Application Insights app\" }\r\n ,\"microsoft.insights/datacollectionendpoints\": { \"SingularDisplayName\": \"Data collection endpoint\" }\r\n ,\"microsoft.insights/datacollectionruleassociations\": { \"SingularDisplayName\": \"Microsoft.Insights data collection rule association\" }\r\n ,\"microsoft.insights/datacollectionrules\": { \"SingularDisplayName\": \"Data collection rule\" }\r\n ,\"microsoft.insights/datacollectionrulesresources\": { \"SingularDisplayName\": \"Data collection rule associated resource\" }\r\n ,\"microsoft.insights/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\r\n ,\"microsoft.insights/diagnosticsettingscategories\": { \"SingularDisplayName\": \"Microsoft.Insights diagnostic settings category\" }\r\n ,\"microsoft.insights/guestdiagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic setting\" }\r\n ,\"microsoft.insights/guestdiagnosticsettingsassociation\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic settings association\" }\r\n ,\"microsoft.insights/logprofiles\": { \"SingularDisplayName\": \"Microsoft.Insights logprofile\" }\r\n ,\"microsoft.insights/metricalerts\": { \"SingularDisplayName\": \"Metric alert rule\" }\r\n ,\"microsoft.insights/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights notification statu\" }\r\n ,\"microsoft.insights/privatelinkscopeoperationstatuses\": { \"SingularDisplayName\": \"Microsoft.insights private link scope operation statuse\" }\r\n ,\"microsoft.insights/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Monitor Private Link Scope\" }\r\n ,\"microsoft.insights/scheduledqueryrules\": { \"SingularDisplayName\": \"Log search alert rule\" }\r\n ,\"microsoft.insights/tenantactiongroups\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action group\" }\r\n ,\"microsoft.insights/tenantactiongroups/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action groups notification statu\" }\r\n ,\"microsoft.insights/vminsightsonboardingstatuses\": { \"SingularDisplayName\": \"Microsoft.Insights VM insights onboarding statuse\" }\r\n ,\"microsoft.insights/webtests\": { \"SingularDisplayName\": \"Application Insights availability test\" }\r\n ,\"microsoft.insights/workbooks\": { \"SingularDisplayName\": \"Azure Workbook\" }\r\n ,\"microsoft.insights/workbooktemplates\": { \"SingularDisplayName\": \"Azure Workbook Template\" }\r\n ,\"microsoft.integrationspaces/spaces\": { \"SingularDisplayName\": \"Integration Environment\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twin\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/assets\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins asset\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/executionplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins execution plan\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/testplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test plan\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/tests\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test\" }\r\n ,\"microsoft.inventory/subscriptioninternalproperties\": { \"SingularDisplayName\": \"Microsoft.Inventory subscription internal property\" }\r\n ,\"microsoft.iotcentral/iotapps\": { \"SingularDisplayName\": \"IoT Central Application\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces\": { \"SingularDisplayName\": \"Firmware analysis workspace\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmware\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares/summaries\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmwares summary\" }\r\n ,\"microsoft.iotoperations/instances\": { \"SingularDisplayName\": \"Azure IoT Operations\" }\r\n ,\"microsoft.iotoperations/instances/brokers\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances broker\" }\r\n ,\"microsoft.iotoperations/instances/brokers/authentications\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authentication\" }\r\n ,\"microsoft.iotoperations/instances/brokers/authorizations\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authorization\" }\r\n ,\"microsoft.iotoperations/instances/brokers/listeners\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers listener\" }\r\n ,\"microsoft.iotoperations/instances/dataflowendpoints\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow endpoint\" }\r\n ,\"microsoft.iotoperations/instances/dataflowprofiles\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profile\" }\r\n ,\"microsoft.iotoperations/instances/dataflowprofiles/dataflows\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profiles dataflow\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instance\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances dataset\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances pipeline\" }\r\n ,\"microsoft.iotoperationsmq/mq\": { \"SingularDisplayName\": \"IoT Operations Ops MQ\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/authentication\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authentication\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/authorization\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authorization\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/listener\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker listener\" }\r\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector topic map\" }\r\n ,\"microsoft.iotoperationsmq/mq/diagnosticservice\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq diagnostic service\" }\r\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector topic map\" }\r\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector topic map\" }\r\n ,\"microsoft.iotoperationsorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator instance\" }\r\n ,\"microsoft.iotoperationsorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator solution\" }\r\n ,\"microsoft.iotoperationsorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator target\" }\r\n ,\"microsoft.iotsecurity/alerttypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity alert type\" }\r\n ,\"microsoft.iotsecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity defender setting\" }\r\n ,\"microsoft.iotsecurity/onpremisesensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity on premise sensor\" }\r\n ,\"microsoft.iotsecurity/recommendationtypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity recommendation type\" }\r\n ,\"microsoft.iotsecurity/sensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity sensor\" }\r\n ,\"microsoft.iotsecurity/sites\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity site\" }\r\n ,\"microsoft.keyvault/managedhsms\": { \"SingularDisplayName\": \"Azure Key Vault Managed HSM\" }\r\n ,\"microsoft.keyvault/vaults\": { \"SingularDisplayName\": \"Key vault\" }\r\n ,\"microsoft.kubernetes/connectedclusters\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc extension\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc namespace\" }\r\n ,\"microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\r\n ,\"microsoft.kubernetesconfiguration/extensiontypes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension type\" }\r\n ,\"microsoft.kubernetesconfiguration/extensiontypes/versions\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension types version\" }\r\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configuration\" }\r\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations/operations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configurations operation\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scope\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private endpoint connection\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private link resource\" }\r\n ,\"microsoft.kubernetesconfiguration/sourcecontrolconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration source control configuration\" }\r\n ,\"microsoft.kubernetesruntime/bgppeers\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime bgp peer\" }\r\n ,\"microsoft.kubernetesruntime/loadbalancers\": { \"SingularDisplayName\": \"Arc Load Balancer\" }\r\n ,\"microsoft.kubernetesruntime/services\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime service\" }\r\n ,\"microsoft.kubernetesruntime/storageclasses\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime storage class\" }\r\n ,\"microsoft.kusto/clusters\": { \"SingularDisplayName\": \"Azure Data Explorer Cluster\" }\r\n ,\"microsoft.kusto/clusters/databases\": { \"SingularDisplayName\": \"Azure Data Explorer Database\" }\r\n ,\"microsoft.labservices/labaccounts\": { \"SingularDisplayName\": \"Lab account\" }\r\n ,\"microsoft.labservices/labaccounts/labs\": { \"SingularDisplayName\": \"Lab\" }\r\n ,\"microsoft.labservices/labplans\": { \"SingularDisplayName\": \"Lab plan\" }\r\n ,\"microsoft.labservices/labs\": { \"SingularDisplayName\": \"Lab\" }\r\n ,\"microsoft.liftrpilot/organizations\": { \"SingularDisplayName\": \"Azure Pilot\" }\r\n ,\"microsoft.loadtestservice/loadtestmappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test mapping\" }\r\n ,\"microsoft.loadtestservice/loadtestprofilemappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test profile mapping\" }\r\n ,\"microsoft.loadtestservice/loadtests\": { \"SingularDisplayName\": \"Azure Load Testing\" }\r\n ,\"microsoft.loadtestservice/playwrightworkspaces\": { \"SingularDisplayName\": \"Playwright Workspace\" }\r\n ,\"microsoft.logic/businessprocesses\": { \"SingularDisplayName\": \"Business Process\" }\r\n ,\"microsoft.logic/integrationaccounts\": { \"SingularDisplayName\": \"Logic app integration account\" }\r\n ,\"microsoft.logic/integrationserviceenvironments\": { \"SingularDisplayName\": \"Integration Service Environment\" }\r\n ,\"microsoft.logic/integrationserviceenvironments/health\": { \"SingularDisplayName\": \"Microsoft.Logic integration service environments health\" }\r\n ,\"microsoft.logic/integrationserviceenvironments/managedapis\": { \"SingularDisplayName\": \"Managed Connector\" }\r\n ,\"microsoft.logic/templates\": { \"SingularDisplayName\": \"Logic Apps Template\" }\r\n ,\"microsoft.logic/workflows\": { \"SingularDisplayName\": \"Logic app\" }\r\n ,\"microsoft.logz/monitors\": { \"SingularDisplayName\": \"Logz.io\" }\r\n ,\"microsoft.logz/monitors/accounts\": { \"SingularDisplayName\": \"Logz sub account\" }\r\n ,\"microsoft.m365/m365resources\": { \"SingularDisplayName\": \"Microsoft.M365 m365 resource\" }\r\n ,\"microsoft.m365consumptionservices/services\": { \"SingularDisplayName\": \"Microsoft.M365ConsumptionServices service\" }\r\n ,\"microsoft.machinelearning/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plan\" }\r\n ,\"microsoft.machinelearning/commitmentplans/commitmentassociations\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plans commitment association\" }\r\n ,\"microsoft.machinelearning/webservices\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) web service\" }\r\n ,\"microsoft.machinelearning/workspaces\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) workspace\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation account\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspace\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspaces project\" }\r\n ,\"microsoft.machinelearningservices/aistudio\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.machinelearningservices/aistudiocreate\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.machinelearningservices/registries\": { \"SingularDisplayName\": \"Azure Machine Learning registry\" }\r\n ,\"microsoft.machinelearningservices/workspaces\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\r\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints\": { \"SingularDisplayName\": \"Machine learning online endpoint\" }\r\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints/deployments\": { \"SingularDisplayName\": \"Machine learning online deployment\" }\r\n ,\"microsoft.machinelearningservices/workspacescreate\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\r\n ,\"microsoft.maintenance/configurationassignments\": { \"SingularDisplayName\": \"Microsoft.Maintenance configuration assignment\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurations\": { \"SingularDisplayName\": \"Maintenance Configuration\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurationsaumbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurationsbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\r\n ,\"microsoft.maintenance/publicmaintenanceconfigurations\": { \"SingularDisplayName\": \"Microsoft.Maintenance public maintenance configuration\" }\r\n ,\"microsoft.managedidentity/identities\": { \"SingularDisplayName\": \"Microsoft.ManagedIdentity identity\" }\r\n ,\"microsoft.managedidentity/userassignedidentities\": { \"SingularDisplayName\": \"Managed Identity\" }\r\n ,\"microsoft.managednetwork/managednetworks\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed network\" }\r\n ,\"microsoft.managednetwork/managednetworks/managednetworkgroups\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network group\" }\r\n ,\"microsoft.managednetwork/managednetworks/managednetworkpeeringpolicies\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network peering policy\" }\r\n ,\"microsoft.managednetworkfabric/accesscontrollists\": { \"SingularDisplayName\": \"Access Control List (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/internetgatewayrules\": { \"SingularDisplayName\": \"Internet Gateway Rule (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/internetgateways\": { \"SingularDisplayName\": \"Internet Gateway (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipcommunities\": { \"SingularDisplayName\": \"IP Community (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipextendedcommunities\": { \"SingularDisplayName\": \"IP Extended Community (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipprefixes\": { \"SingularDisplayName\": \"IP Prefix (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l2isolationdomains\": { \"SingularDisplayName\": \"Layer 2 Isolation Domain (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains\": { \"SingularDisplayName\": \"Layer 3 Isolation Domain (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains/externalnetworks\": { \"SingularDisplayName\": \"External Network (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains/internalnetworks\": { \"SingularDisplayName\": \"Internal Network (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/neighborgroups\": { \"SingularDisplayName\": \"Neighbor Group (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkdevices\": { \"SingularDisplayName\": \"Network Device (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkdevices/networkinterfaces\": { \"SingularDisplayName\": \"Network Interface (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabriccontrollers\": { \"SingularDisplayName\": \"Network Fabric Controller (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabrics\": { \"SingularDisplayName\": \"Network Fabric (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabrics/networktonetworkinterconnects\": { \"SingularDisplayName\": \"Network to Network Interconnect (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabricskus\": { \"SingularDisplayName\": \"Network Fabric SKU (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkmonitors\": { \"SingularDisplayName\": \"Microsoft.ManagedNetworkFabric network monitor\" }\r\n ,\"microsoft.managednetworkfabric/networkpacketbrokers\": { \"SingularDisplayName\": \"Network Packet Broker (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkracks\": { \"SingularDisplayName\": \"Network Rack (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networktaprules\": { \"SingularDisplayName\": \"Network Tap Rule (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networktaps\": { \"SingularDisplayName\": \"Network Tap (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/routepolicies\": { \"SingularDisplayName\": \"Route Policy (Operator Nexus)\" }\r\n ,\"microsoft.managedservices/marketplaceregistrationdefinitions\": { \"SingularDisplayName\": \"Microsoft.ManagedServices marketplace registration definition\" }\r\n ,\"microsoft.managedservices/registrationassignments\": { \"SingularDisplayName\": \"Microsoft.ManagedServices registration assignment\" }\r\n ,\"microsoft.managedservices/registrationdefinitions\": { \"SingularDisplayName\": \"Azure Lighthouse\" }\r\n ,\"microsoft.management/managementgroups\": { \"SingularDisplayName\": \"Microsoft.Management management group\" }\r\n ,\"microsoft.management/managementgroups/microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\r\n ,\"microsoft.management/managementgroups/providers/privatelinkassociations\": { \"SingularDisplayName\": \"Application Gateway\" }\r\n ,\"microsoft.management/managementgroups/providers/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\r\n ,\"microsoft.management/managementgroups/settings\": { \"SingularDisplayName\": \"Microsoft.Management management groups setting\" }\r\n ,\"microsoft.management/managementgroups/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Management management groups subscription\" }\r\n ,\"microsoft.management/servicegroups\": { \"SingularDisplayName\": \"Service group\" }\r\n ,\"microsoft.managementpartner/partners\": { \"SingularDisplayName\": \"Microsoft.ManagementPartner partner\" }\r\n ,\"microsoft.manufacturingplatform/manufacturingdataservices\": { \"SingularDisplayName\": \"Factory Operations Agent in Azure AI Foundry\" }\r\n ,\"microsoft.maps/accounts\": { \"SingularDisplayName\": \"Azure Maps Account\" }\r\n ,\"microsoft.maps/accounts/creators\": { \"SingularDisplayName\": \"Azure Maps Creator Resource\" }\r\n ,\"microsoft.marketplace/privatestores\": { \"SingularDisplayName\": \"Microsoft.Marketplace private store\" }\r\n ,\"microsoft.marketplace/privatestores/adminrequestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores admin request approval\" }\r\n ,\"microsoft.marketplace/privatestores/collections\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collection\" }\r\n ,\"microsoft.marketplace/privatestores/collections/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collections offer\" }\r\n ,\"microsoft.marketplace/privatestores/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores offer\" }\r\n ,\"microsoft.marketplace/privatestores/requestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores request approval\" }\r\n ,\"microsoft.media/mediaservices\": { \"SingularDisplayName\": \"Media service\" }\r\n ,\"microsoft.media/mediaservices/accountfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services account filter\" }\r\n ,\"microsoft.media/mediaservices/assets\": { \"SingularDisplayName\": \"Microsoft.Media media services asset\" }\r\n ,\"microsoft.media/mediaservices/assets/assetfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services assets asset filter\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks\": { \"SingularDisplayName\": \"Microsoft.Media media services assets track\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks/operationresults\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation result\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks/operationstatuses\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation statuse\" }\r\n ,\"microsoft.media/mediaservices/contentkeypolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services content key policy\" }\r\n ,\"microsoft.media/mediaservices/liveevents\": { \"SingularDisplayName\": \"Live event\" }\r\n ,\"microsoft.media/mediaservices/liveevents/liveoutputs\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices live events live output\" }\r\n ,\"microsoft.media/mediaservices/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private endpoint connection\" }\r\n ,\"microsoft.media/mediaservices/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private link resource\" }\r\n ,\"microsoft.media/mediaservices/streamingendpoints\": { \"SingularDisplayName\": \"Streaming Endpoint\" }\r\n ,\"microsoft.media/mediaservices/streaminglocators\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming locator\" }\r\n ,\"microsoft.media/mediaservices/streamingpolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming policy\" }\r\n ,\"microsoft.media/mediaservices/transforms\": { \"SingularDisplayName\": \"Microsoft.Media media services transform\" }\r\n ,\"microsoft.media/mediaservices/transforms/jobs\": { \"SingularDisplayName\": \"Microsoft.Media media services transforms job\" }\r\n ,\"microsoft.mesh/worlds\": { \"SingularDisplayName\": \"Microsoft.Mesh world\" }\r\n ,\"microsoft.mesh/worlds/events\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds event\" }\r\n ,\"microsoft.mesh/worlds/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds events access policy\" }\r\n ,\"microsoft.mesh/worlds/spaces\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds space\" }\r\n ,\"microsoft.mesh/worlds/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds spaces access policy\" }\r\n ,\"microsoft.mesh/worlds/templates\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds template\" }\r\n ,\"microsoft.mesh/worlds/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds templates access policy\" }\r\n ,\"microsoft.messagingcatalog/catalogs\": { \"SingularDisplayName\": \"Microsoft.MessagingCatalog catalog\" }\r\n ,\"microsoft.messagingconnectors/connectors\": { \"SingularDisplayName\": \"Microsoft.MessagingConnectors connector\" }\r\n ,\"microsoft.metaverse/metaverses\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverse\" }\r\n ,\"microsoft.metaverse/metaverses/events\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses event\" }\r\n ,\"microsoft.metaverse/metaverses/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses events access policy\" }\r\n ,\"microsoft.metaverse/metaverses/spaces\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses space\" }\r\n ,\"microsoft.metaverse/metaverses/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses spaces access policy\" }\r\n ,\"microsoft.metaverse/metaverses/templates\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses template\" }\r\n ,\"microsoft.metaverse/metaverses/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses templates access policy\" }\r\n ,\"microsoft.migrate/assessmentprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment project\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/clusters\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments cluster\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments avs assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business case\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/avssummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases avs summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedavsmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated avs machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedsqlentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated sql entity\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/iaassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases iaas summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/overviewsummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases overview summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/paassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases paas summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects group\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessments assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessments avs assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql database\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql instance\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/recommendedassessedentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments recommended assessed entity\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments web app service plan\" }\r\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/hypervcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects hypervcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/importcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects importcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/importsqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects import sql collector\" }\r\n ,\"microsoft.migrate/assessmentprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private endpoint connection\" }\r\n ,\"microsoft.migrate/assessmentprojects/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private link resource\" }\r\n ,\"microsoft.migrate/assessmentprojects/projectsummary\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects project summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/servercollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects servercollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql database\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql instance\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sqlcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/vmwarecollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects vmwarecollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments web app service plan\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app collector\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessments summary\" }\r\n ,\"microsoft.migrate/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate project\" }\r\n ,\"microsoft.migrate/migrateprojects/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database instance\" }\r\n ,\"microsoft.migrate/migrateprojects/databases\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database\" }\r\n ,\"microsoft.migrate/migrateprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects machine\" }\r\n ,\"microsoft.migrate/migrateprojects/migrateevents\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects migrate event\" }\r\n ,\"microsoft.migrate/migrateprojects/solutions\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects solution\" }\r\n ,\"microsoft.migrate/modernizeprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize project\" }\r\n ,\"microsoft.migrate/modernizeprojects/deployedresources\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects deployed resource\" }\r\n ,\"microsoft.migrate/modernizeprojects/jobs\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects job\" }\r\n ,\"microsoft.migrate/modernizeprojects/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects jobs operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/migrateagents\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agent\" }\r\n ,\"microsoft.migrate/modernizeprojects/migrateagents/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agents operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployment\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployments operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloadinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instance\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloadinstances/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instances operation\" }\r\n ,\"microsoft.migrate/movecollections\": { \"SingularDisplayName\": \"Microsoft.Migrate move collection\" }\r\n ,\"microsoft.migrate/movecollections/moveresources\": { \"SingularDisplayName\": \"Microsoft.Migrate move collections move resource\" }\r\n ,\"microsoft.migrate/projects\": { \"SingularDisplayName\": \"Migration project\" }\r\n ,\"microsoft.mission/approvals\": { \"SingularDisplayName\": \"Approval\" }\r\n ,\"microsoft.mission/catalogs\": { \"SingularDisplayName\": \"Catalog\" }\r\n ,\"microsoft.mission/communities\": { \"SingularDisplayName\": \"Community\" }\r\n ,\"microsoft.mission/communities/communityendpoints\": { \"SingularDisplayName\": \"Community endpoint\" }\r\n ,\"microsoft.mission/communities/transithubs\": { \"SingularDisplayName\": \"Transit hub\" }\r\n ,\"microsoft.mission/enclaveconnections\": { \"SingularDisplayName\": \"Enclave connection\" }\r\n ,\"microsoft.mission/externalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission external connection\" }\r\n ,\"microsoft.mission/internalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission internal connection\" }\r\n ,\"microsoft.mission/virtualenclaves\": { \"SingularDisplayName\": \"Enclave\" }\r\n ,\"microsoft.mission/virtualenclaves/enclaveendpoints\": { \"SingularDisplayName\": \"Enclave endpoint\" }\r\n ,\"microsoft.mission/virtualenclaves/endpoints\": { \"SingularDisplayName\": \"Endpoint\" }\r\n ,\"microsoft.mission/virtualenclaves/workloads\": { \"SingularDisplayName\": \"Workload\" }\r\n ,\"microsoft.mixedreality/objectanchorsaccounts\": { \"SingularDisplayName\": \"Object Anchors Account\" }\r\n ,\"microsoft.mixedreality/objectunderstandingaccounts\": { \"SingularDisplayName\": \"Object Understanding Account\" }\r\n ,\"microsoft.mixedreality/remoterenderingaccounts\": { \"SingularDisplayName\": \"Remote Rendering Account\" }\r\n ,\"microsoft.mixedreality/spatialanchorsaccounts\": { \"SingularDisplayName\": \"Spatial Anchors Account\" }\r\n ,\"microsoft.mixedreality/spatialmapsaccounts\": { \"SingularDisplayName\": \"Microsoft.MixedReality spatial maps account\" }\r\n ,\"microsoft.mobilenetwork/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork amf deployment\" }\r\n ,\"microsoft.mobilenetwork/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork cluster service\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks\": { \"SingularDisplayName\": \"Mobile Network\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/datanetworks\": { \"SingularDisplayName\": \"Data Network\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/services\": { \"SingularDisplayName\": \"Service\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/simpolicies\": { \"SingularDisplayName\": \"SIM Policy\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/sites\": { \"SingularDisplayName\": \"Mobile Network Site\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/slices\": { \"SingularDisplayName\": \"Slice\" }\r\n ,\"microsoft.mobilenetwork/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nrf deployment\" }\r\n ,\"microsoft.mobilenetwork/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nssf deployment\" }\r\n ,\"microsoft.mobilenetwork/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork observability service\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes\": { \"SingularDisplayName\": \"Packet Core Control Plane\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes\": { \"SingularDisplayName\": \"Packet Core Data Plane\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes/attacheddatanetworks\": { \"SingularDisplayName\": \"Attached Data Network\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplaneversions\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork packet core control plane version\" }\r\n ,\"microsoft.mobilenetwork/radioaccessnetworks\": { \"SingularDisplayName\": \"Radio Access Network Insights\" }\r\n ,\"microsoft.mobilenetwork/sdmdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sdm deployment\" }\r\n ,\"microsoft.mobilenetwork/simgroups\": { \"SingularDisplayName\": \"SIM Group\" }\r\n ,\"microsoft.mobilenetwork/simgroups/sims\": { \"SingularDisplayName\": \"SIM\" }\r\n ,\"microsoft.mobilenetwork/sims\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sim\" }\r\n ,\"microsoft.mobilenetwork/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork smf deployment\" }\r\n ,\"microsoft.mobilenetwork/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork upf deployment\" }\r\n ,\"microsoft.mobilenetwork/virtualizedmmedeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork virtualized mme deployment\" }\r\n ,\"microsoft.mobilenetwork/vnfagentdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork vnf agent deployment\" }\r\n ,\"microsoft.mobilepacketcore/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore amf deployment\" }\r\n ,\"microsoft.mobilepacketcore/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore cluster service\" }\r\n ,\"microsoft.mobilepacketcore/networkfunctions\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore network function\" }\r\n ,\"microsoft.mobilepacketcore/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nrf deployment\" }\r\n ,\"microsoft.mobilepacketcore/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nssf deployment\" }\r\n ,\"microsoft.mobilepacketcore/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore observability service\" }\r\n ,\"microsoft.mobilepacketcore/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore smf deployment\" }\r\n ,\"microsoft.mobilepacketcore/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore upf deployment\" }\r\n ,\"microsoft.modsimworkbench/workbenches\": { \"SingularDisplayName\": \"Modeling and Simulation Workbench\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers\": { \"SingularDisplayName\": \"Chamber\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/connectors\": { \"SingularDisplayName\": \"Chamber Connector\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/filerequests\": { \"SingularDisplayName\": \"Chamber Data Pipeline File Request\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/files\": { \"SingularDisplayName\": \"Chamber Data Pipeline File\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/licenses\": { \"SingularDisplayName\": \"Chamber License\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/storages\": { \"SingularDisplayName\": \"Chamber Storage\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/workloads\": { \"SingularDisplayName\": \"Chamber VM\" }\r\n ,\"microsoft.modsimworkbench/workbenches/sharedstorages\": { \"SingularDisplayName\": \"Shared Storage\" }\r\n ,\"microsoft.monitor/accounts\": { \"SingularDisplayName\": \"Azure Monitor workspace\" }\r\n ,\"microsoft.monitor/investigations\": { \"SingularDisplayName\": \"Microsoft.Monitor investigation\" }\r\n ,\"microsoft.monitor/pipelinegroups\": { \"SingularDisplayName\": \"Azure Monitor pipeline\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsite\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites agent\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites error summary\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/mysqlservers\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites my sqlserver\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/summaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites summary\" }\r\n ,\"microsoft.netapp/netappaccounts\": { \"SingularDisplayName\": \"NetApp account\" }\r\n ,\"microsoft.netapp/netappaccounts/backuppolicies\": { \"SingularDisplayName\": \"Backup Policy\" }\r\n ,\"microsoft.netapp/netappaccounts/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools\": { \"SingularDisplayName\": \"Capacity pool\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes\": { \"SingularDisplayName\": \"Volume\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/volumequotarules\": { \"SingularDisplayName\": \"User and group quota\" }\r\n ,\"microsoft.netapp/netappaccounts/snapshotpolicies\": { \"SingularDisplayName\": \"Snapshot policy\" }\r\n ,\"microsoft.netapp/netappaccounts/volumegroups\": { \"SingularDisplayName\": \"VolumeGroup\" }\r\n ,\"microsoft.network/applicationgatewayavailablessloptions\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl option\" }\r\n ,\"microsoft.network/applicationgatewayavailablessloptions/predefinedpolicies\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl options predefined policy\" }\r\n ,\"microsoft.network/applicationgateways\": { \"SingularDisplayName\": \"Application gateway\" }\r\n ,\"microsoft.network/applicationgatewaywebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Application Gateway WAF policy\" }\r\n ,\"microsoft.network/applicationsecuritygroups\": { \"SingularDisplayName\": \"Application security group\" }\r\n ,\"microsoft.network/azurefirewalls\": { \"SingularDisplayName\": \"Firewall\" }\r\n ,\"microsoft.network/azurewebcategories\": { \"SingularDisplayName\": \"Microsoft.Network Azure web category\" }\r\n ,\"microsoft.network/bastionhosts\": { \"SingularDisplayName\": \"Bastion\" }\r\n ,\"microsoft.network/cloudserviceslots\": { \"SingularDisplayName\": \"Microsoft.Network cloud service slot\" }\r\n ,\"microsoft.network/connections\": { \"SingularDisplayName\": \"Connection\" }\r\n ,\"microsoft.network/customipprefixes\": { \"SingularDisplayName\": \"Custom IP Prefix\" }\r\n ,\"microsoft.network/ddoscustompolicies\": { \"SingularDisplayName\": \"Microsoft.Network DDoS custom policy\" }\r\n ,\"microsoft.network/ddosprotectionplans\": { \"SingularDisplayName\": \"DDoS protection plan\" }\r\n ,\"microsoft.network/dnsforwardingrulesets\": { \"SingularDisplayName\": \"DNS forwarding ruleset\" }\r\n ,\"microsoft.network/dnsresolverdomainlists\": { \"SingularDisplayName\": \"DNS Domain List\" }\r\n ,\"microsoft.network/dnsresolverpolicies\": { \"SingularDisplayName\": \"DNS Security Policy\" }\r\n ,\"microsoft.network/dnsresolvers\": { \"SingularDisplayName\": \"DNS private resolver\" }\r\n ,\"microsoft.network/dnszones\": { \"SingularDisplayName\": \"DNS zone\" }\r\n ,\"microsoft.network/dscpconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network DSCP configuration\" }\r\n ,\"microsoft.network/expressroutecircuits\": { \"SingularDisplayName\": \"ExpressRoute circuit\" }\r\n ,\"microsoft.network/expressroutecrossconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connection\" }\r\n ,\"microsoft.network/expressroutecrossconnections/peerings\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connections peering\" }\r\n ,\"microsoft.network/expressroutegateways\": { \"SingularDisplayName\": \"ExpressRoute Gateway\" }\r\n ,\"microsoft.network/expressroutegateways/expressrouteconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route gateways express route connection\" }\r\n ,\"microsoft.network/expressrouteports\": { \"SingularDisplayName\": \"ExpressRoute Direct\" }\r\n ,\"microsoft.network/expressrouteportslocations\": { \"SingularDisplayName\": \"Microsoft.Network express route ports location\" }\r\n ,\"microsoft.network/firewallpolicies\": { \"SingularDisplayName\": \"Firewall Policy\" }\r\n ,\"microsoft.network/frontdoors\": { \"SingularDisplayName\": \"Front Door and CDN profiles\" }\r\n ,\"microsoft.network/frontdoorwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Front Door WAF policy\" }\r\n ,\"microsoft.network/ipallocations\": { \"SingularDisplayName\": \"Microsoft.Network IP allocation\" }\r\n ,\"microsoft.network/ipgroups\": { \"SingularDisplayName\": \"IP Group\" }\r\n ,\"microsoft.network/loadbalancers\": { \"SingularDisplayName\": \"Load balancer\" }\r\n ,\"microsoft.network/localnetworkgateways\": { \"SingularDisplayName\": \"Local network gateway\" }\r\n ,\"microsoft.network/natgateways\": { \"SingularDisplayName\": \"NAT gateway\" }\r\n ,\"microsoft.network/networkexperimentprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profile\" }\r\n ,\"microsoft.network/networkexperimentprofiles/experiments\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profiles experiment\" }\r\n ,\"microsoft.network/networkinterfaces\": { \"SingularDisplayName\": \"Network interface\" }\r\n ,\"microsoft.network/networkmanagerconnections\": { \"SingularDisplayName\": \"Microsoft.Network network manager connection\" }\r\n ,\"microsoft.network/networkmanagers\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/connectivityconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/ipampools\": { \"SingularDisplayName\": \"IP address pool\" }\r\n ,\"microsoft.network/networkmanagers/networkgroups\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/routingconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/securityadminconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/securityuserconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/verifierworkspaces\": { \"SingularDisplayName\": \"Verifier Workspace\" }\r\n ,\"microsoft.network/networkprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network profile\" }\r\n ,\"microsoft.network/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group\" }\r\n ,\"microsoft.network/networksecurityperimeters\": { \"SingularDisplayName\": \"Network Security Perimeter\" }\r\n ,\"microsoft.network/networksecurityperimeters/profiles\": { \"SingularDisplayName\": \"Network Security Perimeter Profile\" }\r\n ,\"microsoft.network/networkverifiers\": { \"SingularDisplayName\": \"Virtual Network Verifier\" }\r\n ,\"microsoft.network/networkvirtualappliances\": { \"SingularDisplayName\": \"Microsoft.Network network virtual appliance\" }\r\n ,\"microsoft.network/networkwatchers\": { \"SingularDisplayName\": \"Network Watcher\" }\r\n ,\"microsoft.network/networkwatchers/flowlogs\": { \"SingularDisplayName\": \"Flow log\" }\r\n ,\"microsoft.network/p2svpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Point to Site)\" }\r\n ,\"microsoft.network/privatednszones\": { \"SingularDisplayName\": \"Private DNS zone\" }\r\n ,\"microsoft.network/privatednszones/virtualnetworklinks\": { \"SingularDisplayName\": \"Virtual network link\" }\r\n ,\"microsoft.network/privateendpoints\": { \"SingularDisplayName\": \"Private endpoint\" }\r\n ,\"microsoft.network/privatelinkservices\": { \"SingularDisplayName\": \"Private link service\" }\r\n ,\"microsoft.network/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\r\n ,\"microsoft.network/publicipprefixes\": { \"SingularDisplayName\": \"Public IP Prefix\" }\r\n ,\"microsoft.network/routefilters\": { \"SingularDisplayName\": \"Route filter\" }\r\n ,\"microsoft.network/routetables\": { \"SingularDisplayName\": \"Route table\" }\r\n ,\"microsoft.network/securitypartnerproviders\": { \"SingularDisplayName\": \"Microsoft.Network security partner provider\" }\r\n ,\"microsoft.network/serviceendpointpolicies\": { \"SingularDisplayName\": \"Service endpoint policy\" }\r\n ,\"microsoft.network/trafficmanagergeographichierarchies\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager geographic hierarchy\" }\r\n ,\"microsoft.network/trafficmanagerprofiles\": { \"SingularDisplayName\": \"Traffic Manager profile\" }\r\n ,\"microsoft.network/trafficmanagerusermetricskeys\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager user metrics key\" }\r\n ,\"microsoft.network/virtualhubs\": { \"SingularDisplayName\": \"Microsoft.Network/virtualHub\" }\r\n ,\"microsoft.network/virtualnetworkgateways\": { \"SingularDisplayName\": \"Virtual network gateway\" }\r\n ,\"microsoft.network/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network\" }\r\n ,\"microsoft.network/virtualnetworktaps\": { \"SingularDisplayName\": \"Virtual network terminal access point\" }\r\n ,\"microsoft.network/virtualrouters\": { \"SingularDisplayName\": \"Microsoft.Network virtual router\" }\r\n ,\"microsoft.network/virtualrouters/peerings\": { \"SingularDisplayName\": \"Microsoft.Network virtual routers peering\" }\r\n ,\"microsoft.network/virtualwans\": { \"SingularDisplayName\": \"Virtual WAN\" }\r\n ,\"microsoft.network/vpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Site to Site)\" }\r\n ,\"microsoft.network/vpngateways/vpnconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connection\" }\r\n ,\"microsoft.network/vpngateways/vpnconnections/vpnlinkconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connections VPN link connection\" }\r\n ,\"microsoft.network/vpnserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network VPN server configuration\" }\r\n ,\"microsoft.network/vpnsites\": { \"SingularDisplayName\": \"Microsoft.Network VPN site\" }\r\n ,\"microsoft.network/vpnsites/vpnsitelinks\": { \"SingularDisplayName\": \"Microsoft.Network VPN sites VPN site link\" }\r\n ,\"microsoft.networkanalytics/dataconnectors\": { \"SingularDisplayName\": \"AIOps - Data Connector\" }\r\n ,\"microsoft.networkanalytics/datalakehouses\": { \"SingularDisplayName\": \"AIOps - Data LakeHouse\" }\r\n ,\"microsoft.networkanalytics/dataproducts\": { \"SingularDisplayName\": \"Azure Operator Insights ? Data Product\" }\r\n ,\"microsoft.networkanalytics/dataproducts/datatypes\": { \"SingularDisplayName\": \"Data Type\" }\r\n ,\"microsoft.networkanalytics/dataproductscatalogs\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics data products catalog\" }\r\n ,\"microsoft.networkanalytics/metricsingestionendpoints\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics metrics ingestion endpoint\" }\r\n ,\"microsoft.networkanalytics/networkanalyticsproducts\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics network analytics product\" }\r\n ,\"microsoft.networkcloud/baremetalmachines\": { \"SingularDisplayName\": \"Bare Metal Machine (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/cloudservicesnetworks\": { \"SingularDisplayName\": \"Cloud Services Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clustermanagers\": { \"SingularDisplayName\": \"Cluster Manager (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters\": { \"SingularDisplayName\": \"Cluster (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/baremetalmachinekeysets\": { \"SingularDisplayName\": \"Cluster Bare Metal Machine Key Set (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/bmckeysets\": { \"SingularDisplayName\": \"Cluster Baseboard Management Controller Key Set (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/metricsconfigurations\": { \"SingularDisplayName\": \"Cluster Metrics Configuration (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/edgeclustermachineskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster machine SKU\" }\r\n ,\"microsoft.networkcloud/edgeclusterruntimeversions\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster runtime version\" }\r\n ,\"microsoft.networkcloud/edgeclusters\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster\" }\r\n ,\"microsoft.networkcloud/edgeclusters/nodes\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge clusters node\" }\r\n ,\"microsoft.networkcloud/edgeclusterskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster SKU\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters\": { \"SingularDisplayName\": \"Kubernetes Cluster (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters/agentpools\": { \"SingularDisplayName\": \"Agent Pool (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters/features\": { \"SingularDisplayName\": \"Kubernetes Cluster Feature (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/l2networks\": { \"SingularDisplayName\": \"Layer 2 Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/l3networks\": { \"SingularDisplayName\": \"Layer 3 Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/racks\": { \"SingularDisplayName\": \"Compute Rack (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/rackskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud rack SKU\" }\r\n ,\"microsoft.networkcloud/registrationhubs\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hub\" }\r\n ,\"microsoft.networkcloud/registrationhubs/images\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs image\" }\r\n ,\"microsoft.networkcloud/registrationhubs/machines\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs machine\" }\r\n ,\"microsoft.networkcloud/storageappliances\": { \"SingularDisplayName\": \"Storage Appliance (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/trunkednetworks\": { \"SingularDisplayName\": \"Trunked Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/virtualmachines\": { \"SingularDisplayName\": \"Virtual Machine (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/virtualmachines/consoles\": { \"SingularDisplayName\": \"Virtual Machine Console (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/volumes\": { \"SingularDisplayName\": \"Volume (Operator Nexus)\" }\r\n ,\"microsoft.networkfunction/azuretrafficcollectors\": { \"SingularDisplayName\": \"ExpressRoute traffic collector\" }\r\n ,\"microsoft.networkfunction/meshvpns\": { \"SingularDisplayName\": \"Mesh VPN\" }\r\n ,\"microsoft.nexusidentity/identitycontrollers\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity controller\" }\r\n ,\"microsoft.nexusidentity/identitysets\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity set\" }\r\n ,\"microsoft.notebooks/notebookproxies\": { \"SingularDisplayName\": \"Microsoft.Notebooks notebook proxy\" }\r\n ,\"microsoft.notificationhubs/namespaces\": { \"SingularDisplayName\": \"Notification Hub Namespace\" }\r\n ,\"microsoft.notificationhubs/namespaces/notificationhubs\": { \"SingularDisplayName\": \"Notification Hub\" }\r\n ,\"microsoft.objectstore/osnamespaces\": { \"SingularDisplayName\": \"Microsoft.ObjectStore os namespace\" }\r\n })[tolower(id)]\r\n}\r\n", - "$fxv#3": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_4(id: string) {\r\n dynamic({\r\n \"microsoft.offazure/hypervsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv site\" }\r\n ,\"microsoft.offazure/hypervsites/clusters\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites cluster\" }\r\n ,\"microsoft.offazure/hypervsites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites host\" }\r\n ,\"microsoft.offazure/hypervsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites job\" }\r\n ,\"microsoft.offazure/hypervsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machine\" }\r\n ,\"microsoft.offazure/hypervsites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machines software inventory\" }\r\n ,\"microsoft.offazure/hypervsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites operations statu\" }\r\n ,\"microsoft.offazure/hypervsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites run as account\" }\r\n ,\"microsoft.offazure/importsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure import site\" }\r\n ,\"microsoft.offazure/importsites/deletejobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites delete job\" }\r\n ,\"microsoft.offazure/importsites/exportjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites export job\" }\r\n ,\"microsoft.offazure/importsites/importjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites import job\" }\r\n ,\"microsoft.offazure/importsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites job\" }\r\n ,\"microsoft.offazure/importsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites machine\" }\r\n ,\"microsoft.offazure/mastersites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master site\" }\r\n ,\"microsoft.offazure/mastersites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites operations statu\" }\r\n ,\"microsoft.offazure/mastersites/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private endpoint connection\" }\r\n ,\"microsoft.offazure/mastersites/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private link resource\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql site\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites discovery site data source\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites job\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites operations statu\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites run as account\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqlavailabilitygroups\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql availability group\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqldatabases\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql database\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqlservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql server\" }\r\n ,\"microsoft.offazure/mastersites/webappsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app site\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites discovery site data source\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/extendedmachines\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites extended machine\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/iiswebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web application\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/iiswebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web server\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites runasaccount\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web application\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web server\" }\r\n ,\"microsoft.offazure/serversites\": { \"SingularDisplayName\": \"Microsoft.OffAzure server site\" }\r\n ,\"microsoft.offazure/serversites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites job\" }\r\n ,\"microsoft.offazure/serversites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machine\" }\r\n ,\"microsoft.offazure/serversites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machines software inventory\" }\r\n ,\"microsoft.offazure/serversites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites operations statu\" }\r\n ,\"microsoft.offazure/serversites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites run as account\" }\r\n ,\"microsoft.offazure/vmwaresites\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware site\" }\r\n ,\"microsoft.offazure/vmwaresites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites host\" }\r\n ,\"microsoft.offazure/vmwaresites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites job\" }\r\n ,\"microsoft.offazure/vmwaresites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machine\" }\r\n ,\"microsoft.offazure/vmwaresites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machines software inventory\" }\r\n ,\"microsoft.offazure/vmwaresites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites operations statu\" }\r\n ,\"microsoft.offazure/vmwaresites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites run as account\" }\r\n ,\"microsoft.offazure/vmwaresites/vcenters\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites vcenter\" }\r\n ,\"microsoft.offazurespringboot/springbootsites\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsite\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites error summary\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/springbootapps\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootapp\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/springbootservers\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootserver\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/summaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites summary\" }\r\n ,\"microsoft.onlineexperimentation/workspaces\": { \"SingularDisplayName\": \"Online Experimentation Workspace\" }\r\n ,\"microsoft.openenergyplatform/energyservices\": { \"SingularDisplayName\": \"Azure Data Manager for Energy\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspace\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/applicationregistrations\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application registration\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/applications\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/eventgridfilters\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces event grid filter\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/shares\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/sharesubscriptions\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share subscription\" }\r\n ,\"microsoft.operationalinsights/clusters\": { \"SingularDisplayName\": \"Log Analytics dedicated cluster\" }\r\n ,\"microsoft.operationalinsights/querypacks\": { \"SingularDisplayName\": \"Log Analytics query pack\" }\r\n ,\"microsoft.operationalinsights/workspaces\": { \"SingularDisplayName\": \"Log Analytics workspace\" }\r\n ,\"microsoft.operationsmanagement/managementassociations\": { \"SingularDisplayName\": \"Microsoft.OperationsManagement management association\" }\r\n ,\"microsoft.operationsmanagement/solutions\": { \"SingularDisplayName\": \"Solution\" }\r\n ,\"microsoft.operatorvoicemail/operatorvoicemailinstances\": { \"SingularDisplayName\": \"Microsoft.OperatorVoicemail operator voicemail instance\" }\r\n ,\"microsoft.oraclediscovery/oraclesites\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle site\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites error summary\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/oracledatabases\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle database\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/oracleservers\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle server\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/summaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites summary\" }\r\n ,\"microsoft.orbital/cloudaccessrouters\": { \"SingularDisplayName\": \"Cloud Access Router\" }\r\n ,\"microsoft.orbital/contactprofiles\": { \"SingularDisplayName\": \"Contact Profile\" }\r\n ,\"microsoft.orbital/edgesites\": { \"SingularDisplayName\": \"Edge Site\" }\r\n ,\"microsoft.orbital/geocatalogs\": { \"SingularDisplayName\": \"GeoCatalog\" }\r\n ,\"microsoft.orbital/globalcommunicationssites\": { \"SingularDisplayName\": \"Microsoft.Orbital global communications site\" }\r\n ,\"microsoft.orbital/groundstations\": { \"SingularDisplayName\": \"Ground Station\" }\r\n ,\"microsoft.orbital/l2connections\": { \"SingularDisplayName\": \"L2 Connection\" }\r\n ,\"microsoft.orbital/sdwancontrollers\": { \"SingularDisplayName\": \"SDWAN Controller\" }\r\n ,\"microsoft.orbital/spacecrafts\": { \"SingularDisplayName\": \"Spacecraft\" }\r\n ,\"microsoft.orbital/spacecrafts/contacts\": { \"SingularDisplayName\": \"Contact\" }\r\n ,\"microsoft.orbital/terminals\": { \"SingularDisplayName\": \"Cloud Access Terminal\" }\r\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrence\" }\r\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences/operationresult\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrences operation result\" }\r\n ,\"microsoft.peering/peerasns\": { \"SingularDisplayName\": \"Microsoft.Peering peer asn\" }\r\n ,\"microsoft.peering/peerings\": { \"SingularDisplayName\": \"Peering\" }\r\n ,\"microsoft.peering/peerings/registeredasns\": { \"SingularDisplayName\": \"Registered ASN\" }\r\n ,\"microsoft.peering/peerings/registeredprefixes\": { \"SingularDisplayName\": \"Registered prefix\" }\r\n ,\"microsoft.peering/peeringservices\": { \"SingularDisplayName\": \"Peering Service\" }\r\n ,\"microsoft.peering/peeringservices/prefixes\": { \"SingularDisplayName\": \"Peering Service Prefix\" }\r\n ,\"microsoft.pki/pkis\": { \"SingularDisplayName\": \"Microsoft.Pki PKI\" }\r\n ,\"microsoft.pki/pkis/certificateauthorities\": { \"SingularDisplayName\": \"Microsoft.Pki pkis certificate authority\" }\r\n ,\"microsoft.pki/pkis/enrollmentpolicies\": { \"SingularDisplayName\": \"Microsoft.Pki pkis enrollment policy\" }\r\n ,\"microsoft.policyinsights/attestations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights attestation\" }\r\n ,\"microsoft.policyinsights/policymetadata\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights policy metadata\" }\r\n ,\"microsoft.policyinsights/remediations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights remediation\" }\r\n ,\"microsoft.portal/consoles\": { \"SingularDisplayName\": \"Microsoft.Portal console\" }\r\n ,\"microsoft.portal/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\r\n ,\"microsoft.portal/tenantconfigurations\": { \"SingularDisplayName\": \"Microsoft.Portal tenant configuration\" }\r\n ,\"microsoft.portal/usersettings\": { \"SingularDisplayName\": \"Microsoft.Portal user setting\" }\r\n ,\"microsoft.portal/virtual-privatedashboards\": { \"SingularDisplayName\": \"Private dashboard\" }\r\n ,\"microsoft.portalservices/copilotsettings\": { \"SingularDisplayName\": \"Microsoft.PortalServices copilot setting\" }\r\n ,\"microsoft.portalservices/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\r\n ,\"microsoft.portalservices/extensions\": { \"SingularDisplayName\": \"Portal Extension\" }\r\n ,\"microsoft.portalservices/extensions/deployments\": { \"SingularDisplayName\": \"Extension Deployment\" }\r\n ,\"microsoft.portalservices/extensions/slots\": { \"SingularDisplayName\": \"Extension Slot\" }\r\n ,\"microsoft.portalservices/extensions/versions\": { \"SingularDisplayName\": \"Extension Version\" }\r\n ,\"microsoft.portalservices/settings\": { \"SingularDisplayName\": \"Microsoft.PortalServices setting\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private endpoint connection\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private link resource\" }\r\n ,\"microsoft.powerbi/workspacecollections\": { \"SingularDisplayName\": \"Microsoft.PowerBI workspace collection\" }\r\n ,\"microsoft.powerbidedicated/autoscalevcores\": { \"SingularDisplayName\": \"Microsoft.PowerBIDedicated auto scale vcore\" }\r\n ,\"microsoft.powerbidedicated/capacities\": { \"SingularDisplayName\": \"Power BI Embedded\" }\r\n ,\"microsoft.powerplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.PowerPlatform account\" }\r\n ,\"microsoft.premonition/libraries\": { \"SingularDisplayName\": \"Microsoft.Premonition library\" }\r\n ,\"microsoft.premonition/libraries/analyses\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries analyse\" }\r\n ,\"microsoft.premonition/libraries/samples\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries sample\" }\r\n ,\"microsoft.professionalservice/resources\": { \"SingularDisplayName\": \"Professional Service\" }\r\n ,\"microsoft.programmableconnectivity/gateways\": { \"SingularDisplayName\": \"APC Gateway\" }\r\n ,\"microsoft.programmableconnectivity/operatorapiconnections\": { \"SingularDisplayName\": \"APC Operator API Connection\" }\r\n ,\"microsoft.programmableconnectivity/operatorapiplans\": { \"SingularDisplayName\": \"APC Operator API Plan\" }\r\n ,\"microsoft.proposal/proposals\": { \"SingularDisplayName\": \"Microsoft.Proposal proposal\" }\r\n ,\"microsoft.providerhub/providerregistrations\": { \"SingularDisplayName\": \"Resource Provider as a Service\" }\r\n ,\"microsoft.providerhub/providerregistrations/customrollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.providerhub/providerregistrations/defaultrollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\r\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\r\n ,\"microsoft.providerhubdevtest/regionalstresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest regional stresstest\" }\r\n ,\"microsoft.providerhubdevtest/stresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest stresstest\" }\r\n ,\"microsoft.purview/accounts\": { \"SingularDisplayName\": \"Microsoft Purview account\" }\r\n ,\"microsoft.quantum/provideraccounts\": { \"SingularDisplayName\": \"Microsoft.Quantum provider account\" }\r\n ,\"microsoft.quantum/workspaces\": { \"SingularDisplayName\": \"Quantum Workspace\" }\r\n ,\"microsoft.quota/groupquotas\": { \"SingularDisplayName\": \"Microsoft.Quota group quota\" }\r\n ,\"microsoft.quota/groupquotas/groupquotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas group quota request\" }\r\n ,\"microsoft.quota/groupquotas/quotaallocationrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation request\" }\r\n ,\"microsoft.quota/groupquotas/quotaallocations\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation\" }\r\n ,\"microsoft.quota/groupquotas/subscriptionrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription request\" }\r\n ,\"microsoft.quota/groupquotas/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription\" }\r\n ,\"microsoft.quota/quotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota quota request\" }\r\n ,\"microsoft.quota/quotas\": { \"SingularDisplayName\": \"Microsoft.Quota quota\" }\r\n ,\"microsoft.quota/usages\": { \"SingularDisplayName\": \"Microsoft.Quota usage\" }\r\n ,\"microsoft.recommendationsservice/accounts\": { \"SingularDisplayName\": \"Intelligent Recommendations Account\" }\r\n ,\"microsoft.recommendationsservice/accounts/modeling\": { \"SingularDisplayName\": \"Modeling\" }\r\n ,\"microsoft.recommendationsservice/accounts/serviceendpoints\": { \"SingularDisplayName\": \"Service Endpoint\" }\r\n ,\"microsoft.recoveryservices/replicationeligibilityresults\": { \"SingularDisplayName\": \"Microsoft.RecoveryServices replication eligibility result\" }\r\n ,\"microsoft.recoveryservices/vaults\": { \"SingularDisplayName\": \"Recovery Services vault\" }\r\n ,\"microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems\": { \"SingularDisplayName\": \"Backup Item\" }\r\n ,\"microsoft.recoveryservicesbvtd/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD\" }\r\n ,\"microsoft.recoveryservicesbvtd2/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD2\" }\r\n ,\"microsoft.recoveryservicesintd/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD\" }\r\n ,\"microsoft.recoveryservicesintd2/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD2\" }\r\n ,\"microsoft.redhatopenshift/openshiftclusters\": { \"SingularDisplayName\": \"Azure Red Hat OpenShift cluster\" }\r\n ,\"microsoft.relationships/dependencyof\": { \"SingularDisplayName\": \"Dependency Relationship\" }\r\n ,\"microsoft.relationships/servicegroupmember\": { \"SingularDisplayName\": \"Service group member relationship\" }\r\n ,\"microsoft.relationships/servicegrouprelationships\": { \"SingularDisplayName\": \"Connected Resource\" }\r\n ,\"microsoft.relay/namespaces\": { \"SingularDisplayName\": \"Relay\" }\r\n ,\"microsoft.relay/namespaces/hybridconnections\": { \"SingularDisplayName\": \"Hybrid connection\" }\r\n ,\"microsoft.relay/namespaces/wcfrelays\": { \"SingularDisplayName\": \"WCF relay\" }\r\n ,\"microsoft.resilience/resiliencestates\": { \"SingularDisplayName\": \"Microsoft.Resilience resilience state\" }\r\n ,\"microsoft.resourceconnector/appliances\": { \"SingularDisplayName\": \"Resource bridge\" }\r\n ,\"microsoft.resourcegraph/queries\": { \"SingularDisplayName\": \"Resource Graph query\" }\r\n ,\"microsoft.resourcehealth/availabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth availability statuse\" }\r\n ,\"microsoft.resourcehealth/childavailabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth child availability statuse\" }\r\n ,\"microsoft.resourcehealth/emergingissues\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth emerging issue\" }\r\n ,\"microsoft.resourcehealth/events\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth event\" }\r\n ,\"microsoft.resourcehealth/events/impactedresources\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth events impacted resource\" }\r\n ,\"microsoft.resourcehealth/metadata\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth metadata\" }\r\n ,\"microsoft.resources/builtintemplatespecs\": { \"SingularDisplayName\": \"Built-in template spec\" }\r\n ,\"microsoft.resources/changes\": { \"SingularDisplayName\": \"Microsoft.Resources change\" }\r\n ,\"microsoft.resources/databoundaries\": { \"SingularDisplayName\": \"Microsoft.Resources data boundary\" }\r\n ,\"microsoft.resources/deletedresources\": { \"SingularDisplayName\": \"Recycle Bin\" }\r\n ,\"microsoft.resources/deployments\": { \"SingularDisplayName\": \"Microsoft.Resources deployment\" }\r\n ,\"microsoft.resources/deployments/operations\": { \"SingularDisplayName\": \"Microsoft.Resources deployments operation\" }\r\n ,\"microsoft.resources/deploymentscripts\": { \"SingularDisplayName\": \"Deployment Script\" }\r\n ,\"microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\r\n ,\"microsoft.resources/mobobrokers\": { \"SingularDisplayName\": \"Microsoft.Resources mobo broker\" }\r\n ,\"microsoft.resources/resourcechange\": { \"SingularDisplayName\": \"Change Analysis\" }\r\n ,\"microsoft.resources/resourcechanges\": { \"SingularDisplayName\": \"Resource change\" }\r\n ,\"microsoft.resources/resourcegraphvisualizer\": { \"SingularDisplayName\": \"Resource Graph Visualizer\" }\r\n ,\"microsoft.resources/resourcegroups\": { \"SingularDisplayName\": \"Microsoft.Resources resource group\" }\r\n ,\"microsoft.resources/resources\": { \"SingularDisplayName\": \"Resource\" }\r\n ,\"microsoft.resources/snapshots\": { \"SingularDisplayName\": \"Microsoft.Resources snapshot\" }\r\n ,\"microsoft.resources/subscriptions\": { \"SingularDisplayName\": \"Subscription\" }\r\n ,\"microsoft.resources/subscriptions/resourcegroups\": { \"SingularDisplayName\": \"Resource group\" }\r\n ,\"microsoft.resources/tags\": { \"SingularDisplayName\": \"Microsoft.Resources tag\" }\r\n ,\"microsoft.resources/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\r\n ,\"microsoft.resources/virtualsubscriptionsforresourcepicker\": { \"SingularDisplayName\": \"Subscription\" }\r\n ,\"microsoft.saas/applications\": { \"SingularDisplayName\": \"Software as a Service (classic)\" }\r\n ,\"microsoft.saas/resources\": { \"SingularDisplayName\": \"SaaS\" }\r\n ,\"microsoft.saas/saasresources\": { \"SingularDisplayName\": \"SaaS (classic)\" }\r\n ,\"microsoft.saashub/cloudservices\": { \"SingularDisplayName\": \"Microsoft.SaaSHub cloud service\" }\r\n ,\"microsoft.saashub/cloudservices/hidden\": { \"SingularDisplayName\": \"Microsoft SaaS\" }\r\n ,\"microsoft.saashub/saasresources\": { \"SingularDisplayName\": \"Microsoft.SaaSHub saas resource\" }\r\n ,\"microsoft.salescopilot/conversationintelligencerecordingaccounts\": { \"SingularDisplayName\": \"Microsoft.SalesCopilot conversation intelligence recording account\" }\r\n ,\"microsoft.scheduler/jobcollections\": { \"SingularDisplayName\": \"Scheduler job collection\" }\r\n ,\"microsoft.scheduler/jobcollections/jobs\": { \"SingularDisplayName\": \"Scheduler job\" }\r\n ,\"microsoft.scom/managedinstances\": { \"SingularDisplayName\": \"SCOM managed instance\" }\r\n ,\"microsoft.scvmm/availabilitysets\": { \"SingularDisplayName\": \"Microsoft.ScVmm availability set\" }\r\n ,\"microsoft.scvmm/clouds\": { \"SingularDisplayName\": \"Microsoft.ScVmm cloud\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instance\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances guest agent\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.scvmm/virtualmachines\": { \"SingularDisplayName\": \"SCVMM virtual machine - Azure Arc\" }\r\n ,\"microsoft.scvmm/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine template\" }\r\n ,\"microsoft.scvmm/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual network\" }\r\n ,\"microsoft.scvmm/vmmservers\": { \"SingularDisplayName\": \"SCVMM management server\" }\r\n ,\"microsoft.search/searchservices\": { \"SingularDisplayName\": \"Search service\" }\r\n ,\"microsoft.secretmanagementsampleprovider/forecasts\": { \"SingularDisplayName\": \"Microsoft.SecretManagementSampleProvider forecast\" }\r\n ,\"microsoft.secretsynccontroller/azurekeyvaultsecretproviderclasses\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController Azure key vault secret provider class\" }\r\n ,\"microsoft.secretsynccontroller/secretsyncs\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController secret sync\" }\r\n ,\"microsoft.security/adaptivenetworkhardenings\": { \"SingularDisplayName\": \"Microsoft.Security adaptive network hardening\" }\r\n ,\"microsoft.security/advancedthreatprotectionsettings\": { \"SingularDisplayName\": \"Microsoft.Security advanced threat protection setting\" }\r\n ,\"microsoft.security/alertssuppressionrules\": { \"SingularDisplayName\": \"Microsoft.Security alerts suppression rule\" }\r\n ,\"microsoft.security/apicollections\": { \"SingularDisplayName\": \"Microsoft.Security API collection\" }\r\n ,\"microsoft.security/applications\": { \"SingularDisplayName\": \"Microsoft.Security application\" }\r\n ,\"microsoft.security/assessmentmetadata\": { \"SingularDisplayName\": \"Microsoft.Security assessment metadata\" }\r\n ,\"microsoft.security/assessments\": { \"SingularDisplayName\": \"Microsoft.Security assessment\" }\r\n ,\"microsoft.security/assessments/governanceassignments\": { \"SingularDisplayName\": \"Microsoft.Security assessments governance assignment\" }\r\n ,\"microsoft.security/assessments/subassessments\": { \"SingularDisplayName\": \"Microsoft.Security assessments sub assessment\" }\r\n ,\"microsoft.security/assignments\": { \"SingularDisplayName\": \"Microsoft.Security assignment\" }\r\n ,\"microsoft.security/automations\": { \"SingularDisplayName\": \"Microsoft.Security automation\" }\r\n ,\"microsoft.security/autoprovisioningsettings\": { \"SingularDisplayName\": \"Microsoft.Security auto provisioning setting\" }\r\n ,\"microsoft.security/complianceresults\": { \"SingularDisplayName\": \"Microsoft.Security compliance result\" }\r\n ,\"microsoft.security/compliances\": { \"SingularDisplayName\": \"Microsoft.Security compliance\" }\r\n ,\"microsoft.security/connectors\": { \"SingularDisplayName\": \"Microsoft.Security connector\" }\r\n ,\"microsoft.security/customassessmentautomations\": { \"SingularDisplayName\": \"Microsoft.Security custom assessment automation\" }\r\n ,\"microsoft.security/defenderforstoragesettings\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage setting\" }\r\n ,\"microsoft.security/defenderforstoragesettings/malwarescans\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage settings malware scan\" }\r\n ,\"microsoft.security/devicesecuritygroups\": { \"SingularDisplayName\": \"Microsoft.Security device security group\" }\r\n ,\"microsoft.security/governancerules\": { \"SingularDisplayName\": \"Microsoft.Security governance rule\" }\r\n ,\"microsoft.security/governancerules/operationresults\": { \"SingularDisplayName\": \"Microsoft.Security governance rules operation result\" }\r\n ,\"microsoft.security/healthreports\": { \"SingularDisplayName\": \"Microsoft.Security health report\" }\r\n ,\"microsoft.security/informationprotectionpolicies\": { \"SingularDisplayName\": \"Microsoft.Security information protection policy\" }\r\n ,\"microsoft.security/iotsecuritysolutions\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solution\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics model\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated alert\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated recommendation\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotalerttypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert type\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendationtypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation type\" }\r\n ,\"microsoft.security/locations/alerts\": { \"SingularDisplayName\": \"Security Alert\" }\r\n ,\"microsoft.security/mdeonboardings\": { \"SingularDisplayName\": \"Microsoft.Security mde onboarding\" }\r\n ,\"microsoft.security/pricings\": { \"SingularDisplayName\": \"Defender for Cloud\" }\r\n ,\"microsoft.security/pricings/securityoperators\": { \"SingularDisplayName\": \"Microsoft.Security pricings security operator\" }\r\n ,\"microsoft.security/regulatorycompliancestandards\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standard\" }\r\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance control\" }\r\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols/regulatorycomplianceassessments\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance controls regulatory compliance assessment\" }\r\n ,\"microsoft.security/securescores\": { \"SingularDisplayName\": \"Microsoft.Security secure score\" }\r\n ,\"microsoft.security/securityconnectors\": { \"SingularDisplayName\": \"Microsoft.Security security connector\" }\r\n ,\"microsoft.security/securityconnectors/devops\": { \"SingularDisplayName\": \"Microsoft.Security security connectors devop\" }\r\n ,\"microsoft.security/securitycontacts\": { \"SingularDisplayName\": \"Microsoft.Security security contact\" }\r\n ,\"microsoft.security/sensitivitysettings\": { \"SingularDisplayName\": \"Microsoft.Security sensitivity setting\" }\r\n ,\"microsoft.security/servervulnerabilityassessments\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessment\" }\r\n ,\"microsoft.security/servervulnerabilityassessmentssettings\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessments setting\" }\r\n ,\"microsoft.security/settings\": { \"SingularDisplayName\": \"Microsoft.Security setting\" }\r\n ,\"microsoft.security/standards\": { \"SingularDisplayName\": \"Microsoft.Security standard\" }\r\n ,\"microsoft.security/workspacesettings\": { \"SingularDisplayName\": \"Microsoft.Security workspace setting\" }\r\n ,\"microsoft.securitycopilot/capacities\": { \"SingularDisplayName\": \"Microsoft Security compute capacity\" }\r\n ,\"microsoft.securitydetonation/chambers\": { \"SingularDisplayName\": \"Security Detonation Chamber\" }\r\n ,\"microsoft.securityinsightsarg/sentinel\": { \"SingularDisplayName\": \"Microsoft Sentinel\" }\r\n ,\"microsoft.sentinelplatformservices/sentinelplatformservices\": { \"SingularDisplayName\": \"Microsoft.SentinelPlatformServices sentinel platform service\" }\r\n ,\"microsoft.serialconsole/consoleservices\": { \"SingularDisplayName\": \"Microsoft.SerialConsole console service\" }\r\n ,\"microsoft.serialconsole/serialports\": { \"SingularDisplayName\": \"Microsoft.SerialConsole serial port\" }\r\n ,\"microsoft.servicebus/namespaces\": { \"SingularDisplayName\": \"Service Bus namespace\" }\r\n ,\"microsoft.servicebus/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Service Bus Geo-DR Alias\" }\r\n ,\"microsoft.servicebus/namespaces/queues\": { \"SingularDisplayName\": \"Service Bus queue\" }\r\n ,\"microsoft.servicebus/namespaces/topics\": { \"SingularDisplayName\": \"Service Bus topic\" }\r\n ,\"microsoft.servicebus/namespaces/topics/subscriptions\": { \"SingularDisplayName\": \"Service Bus Subscription\" }\r\n ,\"microsoft.servicefabric/clusters\": { \"SingularDisplayName\": \"Service Fabric cluster\" }\r\n ,\"microsoft.servicefabric/managedclusters\": { \"SingularDisplayName\": \"Service Fabric managed cluster\" }\r\n ,\"microsoft.servicefabricmesh/applications\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh application\" }\r\n ,\"microsoft.servicefabricmesh/applications/services\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications service\" }\r\n ,\"microsoft.servicefabricmesh/applications/services/replicas\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications services replica\" }\r\n ,\"microsoft.servicefabricmesh/gateways\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh gateway\" }\r\n ,\"microsoft.servicefabricmesh/networks\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh network\" }\r\n ,\"microsoft.servicefabricmesh/secrets\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secret\" }\r\n ,\"microsoft.servicefabricmesh/secrets/values\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secrets value\" }\r\n ,\"microsoft.servicefabricmesh/volumes\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh volume\" }\r\n ,\"microsoft.servicelinker/dryruns\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker dryrun\" }\r\n ,\"microsoft.servicelinker/linkers\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker linker\" }\r\n ,\"microsoft.servicenetworking/trafficcontrollers\": { \"SingularDisplayName\": \"Application Gateway for Containers\" }\r\n ,\"microsoft.serviceshub/connectors\": { \"SingularDisplayName\": \"Services Hub Connector\" }\r\n ,\"microsoft.signalrservice/signalr\": { \"SingularDisplayName\": \"SignalR\" }\r\n ,\"microsoft.signalrservice/signalr/replicas\": { \"SingularDisplayName\": \"SignalR Replica\" }\r\n ,\"microsoft.signalrservice/webpubsub\": { \"SingularDisplayName\": \"Web PubSub Service\" }\r\n ,\"microsoft.signalrservice/webpubsub/replicas\": { \"SingularDisplayName\": \"Web PubSub Service Replica\" }\r\n ,\"microsoft.skytap/billingnodes\": { \"SingularDisplayName\": \"Microsoft.Skytap billing node\" }\r\n ,\"microsoft.skytap/interfaces\": { \"SingularDisplayName\": \"Microsoft.Skytap interface\" }\r\n ,\"microsoft.skytap/nodes\": { \"SingularDisplayName\": \"Microsoft.Skytap node\" }\r\n ,\"microsoft.softwareplan/hybridusebenefits\": { \"SingularDisplayName\": \"Microsoft.SoftwarePlan hybrid use benefit\" }\r\n ,\"microsoft.solutions/applicationdefinitions\": { \"SingularDisplayName\": \"Service catalog managed application definition\" }\r\n ,\"microsoft.solutions/applications\": { \"SingularDisplayName\": \"Managed application\" }\r\n ,\"microsoft.solutions/jitrequests\": { \"SingularDisplayName\": \"Microsoft.Solutions JIT request\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts\": { \"SingularDisplayName\": \"Landing zone account\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\r\n ,\"microsoft.sovereign/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\r\n ,\"microsoft.sovereign/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\r\n ,\"microsoft.sovereign/transparencylogs\": { \"SingularDisplayName\": \"Transparency log\" }\r\n ,\"microsoft.sql/azuresql\": { \"SingularDisplayName\": \"Azure SQL resource\" }\r\n ,\"microsoft.sql/instancepools\": { \"SingularDisplayName\": \"Instance pool\" }\r\n ,\"microsoft.sql/managedinstances\": { \"SingularDisplayName\": \"SQL managed instance\" }\r\n ,\"microsoft.sql/managedinstances/databases\": { \"SingularDisplayName\": \"Managed database\" }\r\n ,\"microsoft.sql/servers\": { \"SingularDisplayName\": \"SQL server\" }\r\n ,\"microsoft.sql/servers/databases\": { \"SingularDisplayName\": \"SQL database\" }\r\n ,\"microsoft.sql/servers/elasticpools\": { \"SingularDisplayName\": \"SQL elastic pool\" }\r\n ,\"microsoft.sql/servers/jobagents\": { \"SingularDisplayName\": \"Elastic Job agent\" }\r\n ,\"microsoft.sql/virtualclusters\": { \"SingularDisplayName\": \"Virtual cluster\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine group\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups/availabilitygrouplisteners\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine groups availability group listener\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachines\": { \"SingularDisplayName\": \"SQL virtual machine\" }\r\n ,\"microsoft.standbypool/standbycontainergrouppools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pool\" }\r\n ,\"microsoft.standbypool/standbycontainergrouppools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pools runtime view\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pool\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools runtime view\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools/standbyvirtualmachines\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools standby virtual machine\" }\r\n ,\"microsoft.storage/storageaccounts\": { \"SingularDisplayName\": \"Storage account\" }\r\n ,\"microsoft.storageactions/storagetasks\": { \"SingularDisplayName\": \"Storage task - Azure Storage Actions\" }\r\n ,\"microsoft.storagecache/amlfilesystems\": { \"SingularDisplayName\": \"Azure Managed Lustre\" }\r\n ,\"microsoft.storagecache/caches\": { \"SingularDisplayName\": \"HPC cache\" }\r\n ,\"microsoft.storagediscovery/storagediscoveryworkspaces\": { \"SingularDisplayName\": \"Storage Discovery workspace\" }\r\n ,\"microsoft.storagehub/all\": { \"SingularDisplayName\": \"All resources\" }\r\n ,\"microsoft.storagehub/policycomplianceresources\": { \"SingularDisplayName\": \"Policy compliance\" }\r\n ,\"microsoft.storageinsights/storagecollectionrules\": { \"SingularDisplayName\": \"Microsoft.StorageInsights storage collection rule\" }\r\n ,\"microsoft.storagemover/storagemovers\": { \"SingularDisplayName\": \"Storage mover\" }\r\n ,\"microsoft.storagepool/diskpools\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pool\" }\r\n ,\"microsoft.storagepool/diskpools/iscsitargets\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pools iscsi target\" }\r\n ,\"microsoft.storagesync/storagesyncservices\": { \"SingularDisplayName\": \"Storage Sync Service\" }\r\n ,\"microsoft.storagetasks/storagetasks\": { \"SingularDisplayName\": \"Microsoft.StorageTasks storage task\" }\r\n ,\"microsoft.storsimple/managers\": { \"SingularDisplayName\": \"StorSimple device manager\" }\r\n ,\"microsoft.storsimple/managers/accesscontrolrecords\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers access control record\" }\r\n ,\"microsoft.storsimple/managers/bandwidthsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers bandwidth setting\" }\r\n ,\"microsoft.storsimple/managers/certificates\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers certificate\" }\r\n ,\"microsoft.storsimple/managers/devices\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers device\" }\r\n ,\"microsoft.storsimple/managers/devices/alertsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices alert setting\" }\r\n ,\"microsoft.storsimple/managers/devices/backuppolicies\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policy\" }\r\n ,\"microsoft.storsimple/managers/devices/backuppolicies/schedules\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policies schedule\" }\r\n ,\"microsoft.storsimple/managers/devices/backupschedulegroups\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup schedule group\" }\r\n ,\"microsoft.storsimple/managers/devices/chapsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices chap setting\" }\r\n ,\"microsoft.storsimple/managers/devices/fileservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileserver\" }\r\n ,\"microsoft.storsimple/managers/devices/fileservers/shares\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileservers share\" }\r\n ,\"microsoft.storsimple/managers/devices/iscsiservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiserver\" }\r\n ,\"microsoft.storsimple/managers/devices/iscsiservers/disks\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiservers disk\" }\r\n ,\"microsoft.storsimple/managers/devices/jobs\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices job\" }\r\n ,\"microsoft.storsimple/managers/devices/networksettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices network setting\" }\r\n ,\"microsoft.storsimple/managers/devices/securitysettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices security setting\" }\r\n ,\"microsoft.storsimple/managers/devices/timesettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices time setting\" }\r\n ,\"microsoft.storsimple/managers/devices/updatesummary\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices update summary\" }\r\n ,\"microsoft.storsimple/managers/devices/volumecontainers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume container\" }\r\n ,\"microsoft.storsimple/managers/devices/volumecontainers/volumes\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume containers volume\" }\r\n ,\"microsoft.storsimple/managers/encryptionsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers encryption setting\" }\r\n ,\"microsoft.storsimple/managers/extendedinformation\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers extended information\" }\r\n ,\"microsoft.storsimple/managers/storageaccountcredentials\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage account credential\" }\r\n ,\"microsoft.storsimple/managers/storagedomains\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage domain\" }\r\n ,\"microsoft.streamanalytics/clusters\": { \"SingularDisplayName\": \"Stream Analytics cluster\" }\r\n ,\"microsoft.streamanalytics/streamingjobs\": { \"SingularDisplayName\": \"Stream Analytics job\" }\r\n ,\"microsoft.subscription/aliases\": { \"SingularDisplayName\": \"Microsoft.Subscription aliase\" }\r\n ,\"microsoft.subscription/changetenantrequest\": { \"SingularDisplayName\": \"Microsoft.Subscription change tenant request\" }\r\n ,\"microsoft.subscription/policies\": { \"SingularDisplayName\": \"Microsoft.Subscription policy\" }\r\n ,\"microsoft.subscription/subscriptiondefinitions\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription definition\" }\r\n ,\"microsoft.subscription/subscriptionoperations\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription operation\" }\r\n ,\"microsoft.support/fileworkspaces\": { \"SingularDisplayName\": \"Microsoft.Support file workspace\" }\r\n ,\"microsoft.support/fileworkspaces/files\": { \"SingularDisplayName\": \"Microsoft.Support file workspaces file\" }\r\n ,\"microsoft.support/services\": { \"SingularDisplayName\": \"Microsoft.Support service\" }\r\n ,\"microsoft.support/services/problemclassifications\": { \"SingularDisplayName\": \"Microsoft.Support services problem classification\" }\r\n ,\"microsoft.support/supporttickets\": { \"SingularDisplayName\": \"Support Request\" }\r\n ,\"microsoft.sustainabilityservices/calculations\": { \"SingularDisplayName\": \"Project Sustainability Calculator\" }\r\n ,\"microsoft.symphony/instances\": { \"SingularDisplayName\": \"Microsoft.Symphony instance\" }\r\n ,\"microsoft.symphony/solutions\": { \"SingularDisplayName\": \"Microsoft.Symphony solution\" }\r\n ,\"microsoft.symphony/targets\": { \"SingularDisplayName\": \"Microsoft.Symphony target\" }\r\n ,\"microsoft.synapse/privatelinkhubs\": { \"SingularDisplayName\": \"Synapse private link hub\" }\r\n ,\"microsoft.synapse/workspaces\": { \"SingularDisplayName\": \"Synapse workspace\" }\r\n ,\"microsoft.synapse/workspaces/bigdatapools\": { \"SingularDisplayName\": \"Apache Spark pool\" }\r\n ,\"microsoft.synapse/workspaces/kustopools\": { \"SingularDisplayName\": \"Data Explorer pool\" }\r\n ,\"microsoft.synapse/workspaces/kustopools/databases\": { \"SingularDisplayName\": \"Data Explorer Database\" }\r\n ,\"microsoft.synapse/workspaces/scopepools\": { \"SingularDisplayName\": \"SCOPE pool\" }\r\n ,\"microsoft.synapse/workspaces/sqlpools\": { \"SingularDisplayName\": \"Dedicated SQL pool\" }\r\n ,\"microsoft.syntex/accounts\": { \"SingularDisplayName\": \"Microsoft.Syntex account\" }\r\n ,\"microsoft.syntex/documentprocessors\": { \"SingularDisplayName\": \"Microsoft.Syntex document processor\" }\r\n ,\"microsoft.test/healthdataaiservices\": { \"SingularDisplayName\": \"Azure Health Data and AI Services\" }\r\n ,\"microsoft.timeseriesinsights/environments\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environment\" }\r\n ,\"microsoft.timeseriesinsights/environments/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments access policy\" }\r\n ,\"microsoft.timeseriesinsights/environments/eventsources\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments event source\" }\r\n ,\"microsoft.timeseriesinsights/environments/referencedatasets\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments reference data set\" }\r\n ,\"microsoft.toolchainorchestrator/activations\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator activation\" }\r\n ,\"microsoft.toolchainorchestrator/campaigns\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaign\" }\r\n ,\"microsoft.toolchainorchestrator/campaigns/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaigns version\" }\r\n ,\"microsoft.toolchainorchestrator/catalogs\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalog\" }\r\n ,\"microsoft.toolchainorchestrator/catalogs/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalogs version\" }\r\n ,\"microsoft.toolchainorchestrator/diagnostics\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator diagnostic\" }\r\n ,\"microsoft.toolchainorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instance\" }\r\n ,\"microsoft.toolchainorchestrator/instances/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instances version\" }\r\n ,\"microsoft.toolchainorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solution\" }\r\n ,\"microsoft.toolchainorchestrator/solutions/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solutions version\" }\r\n ,\"microsoft.toolchainorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator target\" }\r\n ,\"microsoft.toolchainorchestrator/targets/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator targets version\" }\r\n ,\"microsoft.updatemanager/updaterules\": { \"SingularDisplayName\": \"Update Rule\" }\r\n ,\"microsoft.usagebilling/accounts\": { \"SingularDisplayName\": \"Microsoft.UsageBilling account\" }\r\n ,\"microsoft.usagebilling/accounts/dataexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts data export\" }\r\n ,\"microsoft.usagebilling/accounts/inputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts input\" }\r\n ,\"microsoft.usagebilling/accounts/metricexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts metric export\" }\r\n ,\"microsoft.usagebilling/accounts/pav2outputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pav2output\" }\r\n ,\"microsoft.usagebilling/accounts/pipelines\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipeline\" }\r\n ,\"microsoft.usagebilling/accounts/pipelines/outputselectors\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipelines output selector\" }\r\n ,\"microsoft.verifiedid/authorities\": { \"SingularDisplayName\": \"Microsoft.VerifiedId authority\" }\r\n ,\"microsoft.videoindexer/accounts\": { \"SingularDisplayName\": \"Azure AI Video Indexer\" }\r\n ,\"microsoft.virtualmachineimages/imagetemplates\": { \"SingularDisplayName\": \"Image template\" }\r\n ,\"microsoft.visualstudio/account\": { \"SingularDisplayName\": \"Azure DevOps organization\" }\r\n ,\"microsoft.vmware/resourcepools\": { \"SingularDisplayName\": \"Microsoft.VMware resource pool\" }\r\n ,\"microsoft.vmware/vcenters\": { \"SingularDisplayName\": \"Microsoft.VMware vcenter\" }\r\n ,\"microsoft.vmware/vcenters/inventoryitems\": { \"SingularDisplayName\": \"Microsoft.VMware vcenters inventory item\" }\r\n ,\"microsoft.vmware/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine\" }\r\n ,\"microsoft.vmware/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine template\" }\r\n ,\"microsoft.vmware/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.VMware virtual network\" }\r\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudnodes\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud node\" }\r\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudservices\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud service\" }\r\n ,\"microsoft.vmwarecloudsimple/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple virtual machine\" }\r\n ,\"microsoft.vnfmanager/devices\": { \"SingularDisplayName\": \"Microsoft.VnfManager device\" }\r\n ,\"microsoft.vnfmanager/vendors\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendor\" }\r\n ,\"microsoft.vnfmanager/vendors/skus\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendors SKU\" }\r\n ,\"microsoft.vnfmanager/vnfs\": { \"SingularDisplayName\": \"Microsoft.VnfManager vnf\" }\r\n ,\"microsoft.voiceservices/communicationsgateways\": { \"SingularDisplayName\": \"Communications Gateway\" }\r\n ,\"microsoft.voiceservices/communicationsgateways/testlines\": { \"SingularDisplayName\": \"Communications Gateway Test Line\" }\r\n ,\"microsoft.vsonline/accounts\": { \"SingularDisplayName\": \"Microsoft.VSOnline account\" }\r\n ,\"microsoft.vsonline/plans\": { \"SingularDisplayName\": \"Visual Studio Online Plan\" }\r\n ,\"microsoft.web/certificates\": { \"SingularDisplayName\": \"Microsoft.Web certificate\" }\r\n ,\"microsoft.web/connectiongateways\": { \"SingularDisplayName\": \"App Service on-premises data gateway\" }\r\n ,\"microsoft.web/connections\": { \"SingularDisplayName\": \"App Service API connection\" }\r\n ,\"microsoft.web/containerapps\": { \"SingularDisplayName\": \"Microsoft.Web container app\" }\r\n ,\"microsoft.web/containerapps/revisions\": { \"SingularDisplayName\": \"Microsoft.Web container apps revision\" }\r\n ,\"microsoft.web/customapis\": { \"SingularDisplayName\": \"Logic apps custom connector\" }\r\n ,\"microsoft.web/deletedsites\": { \"SingularDisplayName\": \"Microsoft.Web deleted site\" }\r\n ,\"microsoft.web/hostingenvironments\": { \"SingularDisplayName\": \"App Service Environment\" }\r\n ,\"microsoft.web/ishostingenvironmentnameavailable\": { \"SingularDisplayName\": \"Microsoft.Web ishostingenvironmentnameavailable\" }\r\n ,\"microsoft.web/kubeenvironments\": { \"SingularDisplayName\": \"App Service Kubernetes Environment\" }\r\n ,\"microsoft.web/logicappstemplate\": { \"SingularDisplayName\": \"Logic Apps Template\" }\r\n ,\"microsoft.web/publishingusers\": { \"SingularDisplayName\": \"Microsoft.Web publishing user\" }\r\n ,\"microsoft.web/serverfarms\": { \"SingularDisplayName\": \"App Service plan\" }\r\n ,\"microsoft.web/sites\": { \"SingularDisplayName\": \"App Service web app\" }\r\n ,\"microsoft.web/sites/slots\": { \"SingularDisplayName\": \"App Service deployment slot\" }\r\n ,\"microsoft.web/sourcecontrols\": { \"SingularDisplayName\": \"Microsoft.Web sourcecontrol\" }\r\n ,\"microsoft.web/staticsites\": { \"SingularDisplayName\": \"Static Web App\" }\r\n ,\"microsoft.weightsandbiases/instances\": { \"SingularDisplayName\": \"Azure Native Weights & Biases Cloud Service\" }\r\n ,\"microsoft.whiteboxcadlprovider/whiteboxresources\": { \"SingularDisplayName\": \"Microsoft.WhiteBoxCadlProvider white box resource\" }\r\n ,\"microsoft.windows365/cloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.Windows365 cloud pc delegated msi\" }\r\n ,\"microsoft.windowsesu/multipleactivationkeys\": { \"SingularDisplayName\": \"Microsoft.WindowsESU multiple activation key\" }\r\n ,\"microsoft.windowsiot/deviceservices\": { \"SingularDisplayName\": \"Microsoft.WindowsIoT device service\" }\r\n ,\"microsoft.windowspushnotificationservices/registrations\": { \"SingularDisplayName\": \"Windows Push Notification Service\" }\r\n ,\"microsoft.workloadmonitor/monitors\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitor\" }\r\n ,\"microsoft.workloadmonitor/monitors/history\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitors history\" }\r\n ,\"microsoft.workloads/configurationvalidationresults\": { \"SingularDisplayName\": \"Microsoft.Workloads configuration validation result\" }\r\n ,\"microsoft.workloads/connectors\": { \"SingularDisplayName\": \"Microsoft.Workloads connector\" }\r\n ,\"microsoft.workloads/connectors/acssbackups\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors acss backup\" }\r\n ,\"microsoft.workloads/connectors/amsinsights\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors ams insight\" }\r\n ,\"microsoft.workloads/connectors/sapvirtualinstancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors sap virtual instance monitor\" }\r\n ,\"microsoft.workloads/epicvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for Epic solution\" }\r\n ,\"microsoft.workloads/insights\": { \"SingularDisplayName\": \"Microsoft.Workloads insight\" }\r\n ,\"microsoft.workloads/instancegroupmonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance group monitor\" }\r\n ,\"microsoft.workloads/instancehealthdefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definition\" }\r\n ,\"microsoft.workloads/instancehealthdefinitions/signaldefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definitions signal definition\" }\r\n ,\"microsoft.workloads/instancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance monitor\" }\r\n ,\"microsoft.workloads/monitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP solutions\" }\r\n ,\"microsoft.workloads/oraclevirtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instance\" }\r\n ,\"microsoft.workloads/oraclevirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instances database instance\" }\r\n ,\"microsoft.workloads/phpworkloads\": { \"SingularDisplayName\": \"Microsoft.Workloads php workload\" }\r\n ,\"microsoft.workloads/phpworkloads/wordpressinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads php workloads wordpress instance\" }\r\n ,\"microsoft.workloads/sapdiscoverysites\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery site\" }\r\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instance\" }\r\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances/serverinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instances server instance\" }\r\n ,\"microsoft.workloads/sapvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/applicationinstances\": { \"SingularDisplayName\": \"App server instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/centralinstances\": { \"SingularDisplayName\": \"Central service instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Database for SAP solutions\" }\r\n ,\"microsoft.workloads/virtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instance\" }\r\n ,\"microsoft.workloads/virtualinstances/components\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instances component\" }\r\n ,\"microsoft.workloads/workloadinstance\": { \"SingularDisplayName\": \"My Resource\" }\r\n ,\"microsoft.zerotrustsegmentation/segmentationmanagers\": { \"SingularDisplayName\": \"Segmentation Manager\" }\r\n ,\"mongodb.atlas/organizations\": { \"SingularDisplayName\": \"MongoDB Atlas Organization\" }\r\n ,\"neon.postgres/organizations\": { \"SingularDisplayName\": \"Neon Serverless Postgres Organization\" }\r\n ,\"newrelic.observability/monitors\": { \"SingularDisplayName\": \"New Relic\" }\r\n ,\"nginx.nginxplus/nginxdeployments\": { \"SingularDisplayName\": \"NGINXaaS\" }\r\n ,\"oracle.database/autonomousdatabases\": { \"SingularDisplayName\": \"Autonomous Database\" }\r\n ,\"oracle.database/basedb\": { \"SingularDisplayName\": \"Autonomous Database\" }\r\n ,\"oracle.database/cloudexadatainfrastructures\": { \"SingularDisplayName\": \"Oracle Exadata Infrastructure\" }\r\n ,\"oracle.database/cloudvmclusters\": { \"SingularDisplayName\": \"Oracle Exadata VM Cluster\" }\r\n ,\"oracle.database/exadbvmclusters\": { \"SingularDisplayName\": \"Oracle Exascale VM Cluster\" }\r\n ,\"oracle.database/exascaledbstoragevaults\": { \"SingularDisplayName\": \"Oracle Exascale DB Storage Vault\" }\r\n ,\"oracle.database/networkanchors\": { \"SingularDisplayName\": \"Network Anchor\" }\r\n ,\"oracle.database/oraclesubscriptions\": { \"SingularDisplayName\": \"OracleSubscription\" }\r\n ,\"oracle.database/resourceanchors\": { \"SingularDisplayName\": \"Resource Anchor\" }\r\n ,\"paloaltonetworks.cloudngfw/firewalls\": { \"SingularDisplayName\": \"Cloud NGFW by Palo Alto Networks\" }\r\n ,\"paloaltonetworks.cloudngfw/globalrulestacks\": { \"SingularDisplayName\": \"Global Rulestack\" }\r\n ,\"paloaltonetworks.cloudngfw/localrulestacks\": { \"SingularDisplayName\": \"Local Rulestack for Cloud NGFW by Palo Alto Networks\" }\r\n ,\"pinecone.vectordb/organizations\": { \"SingularDisplayName\": \"Azure Native Pinecone Cloud Service\" }\r\n ,\"purestorage.block/reservations\": { \"SingularDisplayName\": \"Azure Native Pure Storage Cloud Service\" }\r\n ,\"purestorage.block/storagepools\": { \"SingularDisplayName\": \"Storage pool\" }\r\n ,\"purestorage.block/storagepools/avsstoragecontainers\": { \"SingularDisplayName\": \"PureStorage.Block storage pools avs storage container\" }\r\n })[tolower(id)]\r\n}\r\n", - "$fxv#4": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_5(id: string) {\r\n dynamic({\r\n \"qumulo.qaas/storages\": { \"SingularDisplayName\": \"Qumulo.QaaS storage\" }\r\n ,\"qumulo.storage/filesystems\": { \"SingularDisplayName\": \"Azure Native Qumulo Scalable File Service\" }\r\n ,\"solarwinds.observability/organizations\": { \"SingularDisplayName\": \"SolarWinds Observability\" }\r\n ,\"splitio.experimentation/experimentationworkspaces\": { \"SingularDisplayName\": \"Split Experimentation Workspace\" }\r\n ,\"wandisco.fusion/migrators\": { \"SingularDisplayName\": \"LiveData Migrator\" }\r\n ,\"wandisco.fusion/migrators/datatransferagents\": { \"SingularDisplayName\": \"Data Transfer Agent\" }\r\n ,\"wandisco.fusion/migrators/exclusiontemplates\": { \"SingularDisplayName\": \"Exclusion\" }\r\n ,\"wandisco.fusion/migrators/livedatamigrations\": { \"SingularDisplayName\": \"Migration\" }\r\n ,\"wandisco.fusion/migrators/metadatamigrations\": { \"SingularDisplayName\": \"Metadata Migration\" }\r\n ,\"wandisco.fusion/migrators/metadatatargets\": { \"SingularDisplayName\": \"Metadata Target\" }\r\n ,\"wandisco.fusion/migrators/pathmappings\": { \"SingularDisplayName\": \"Path Mapping\" }\r\n ,\"wandisco.fusion/migrators/targets\": { \"SingularDisplayName\": \"Target\" }\r\n ,\"wandisco.fusion/migrators/verifications\": { \"SingularDisplayName\": \"Verification\" }\r\n })[tolower(id)]\r\n}\r\n", - "$fxv#5": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n// resource_type\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData')\r\nresource_type(id: string) {\r\n coalesce(_resource_type_1(id), _resource_type_2(id), _resource_type_3(id), _resource_type_4(id), _resource_type_5(id))\r\n}\r\n", - "$fxv#6": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Common utility functions\r\n//\r\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\r\n//======================================================================================================================\r\n\r\n\r\n//===| Date functions |=================================================================================================\r\n\r\n// monthstring\r\n.create-or-alter function \r\nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \r\nmonthstring(['date']: datetime, length: int = 9)\r\n{\r\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\r\n}\r\n\r\n// datestring\r\n.create-or-alter function \r\nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \r\ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n let month = (d: datetime) { monthstring(d, 3) };\r\n let endDate = iff(end == datetime('0001-01-01'), start, end);\r\n let sameDate = startofday(start) == startofday(endDate);\r\n let sameMonth = startofmonth(start) == startofmonth(endDate);\r\n let sameYear = startofyear(start) == startofyear(endDate);\r\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\r\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\r\n let currentYear = sameYear and startofyear(start) == startofyear(now());\r\n case(\r\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\r\n fullYear,\r\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\r\n // 1 full mo, same year | Mmm yyyy\r\n fullMonth and sameMonth and sameYear,\r\n strcat(month(start), ' ', getyear(start)),\r\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\r\n fullMonth and sameYear,\r\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\r\n fullMonth and not(sameYear),\r\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\r\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\r\n sameDate,\r\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\r\n not(fullMonth) and sameMonth and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\r\n not(fullMonth) and not(sameMonth) and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\r\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\r\n )\r\n}\r\n\r\n// daterange\r\n.create-or-alter function \r\nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \r\ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n datestring(start, end)\r\n}\r\n\r\n// monthsago\r\n.create-or-alter function \r\nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\r\nmonthsago(months: int)\r\n{\r\n datetime_add('month', -months, startofmonth(now()))\r\n}\r\n\r\n\r\n//===| Number functions |===============================================================================================\r\n// NOTE: Must be defined before string converters\r\n\r\n// delta\r\n.create-or-alter function \r\nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \r\ndelta(oldval: double, newval: double)\r\n{\r\n (newval - todouble(oldval))/oldval\r\n}\r\n\r\n// percentOfTotal\r\n// NOTE: Must be before percent() function\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercentOfTotal(t: (Count: long), tot: long)\r\n{\r\n let total = todouble(tot);\r\n t \r\n | extend Percent = round(Count / total * 100, 3) \r\n | order by Count desc\r\n}\r\n\r\n// percent\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercent(t: (Count: long))\r\n{\r\n let total = todouble(toscalar(t | summarize sum(Count)));\r\n percentOfTotal(t, total)\r\n}\r\n\r\n// plusminus\r\n.create-or-alter function \r\nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\r\nplusminus(val: string)\r\n{\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, val, strcat('+', val))\r\n}\r\n\r\n// updown\r\n.create-or-alter function \r\nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\r\nupdown(val: string)\r\n{\r\n // TODO: Handle 0\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\r\n}\r\n\r\n\r\n//===| String functions |===============================================================================================\r\n\r\n// percentstring\r\n// NOTE: Must be defined before deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\r\npercentstring(num: double, total: double = 1.0, places: int = 9)\r\n{\r\n let value = 1.0 * num / total * 100;\r\n strcat(case(\r\n places != 9, round(value, places),\r\n value < 10, round(value, 2),\r\n round(value, 1)\r\n ), '%')\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// arraystring\r\n.create-or-alter function \r\nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\r\narraystring(arr: dynamic)\r\n{\r\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\r\n tostring(arr)\r\n , @'^\\[\"', '')\r\n , @'\"\\]$', '')\r\n , @'^, ', '')\r\n , @', $', '')\r\n , @'^\\[]$', '')\r\n , '\",\"', ', ')\r\n}\r\n\r\n// deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\r\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\r\n{\r\n let d = delta(oldval, newval);\r\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\r\n}\r\n\r\n// diffstring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\r\ndiffstring(oldval: double, newval: double, places: int = 1)\r\n{\r\n plusminus(round(newval - oldval, places))\r\n}\r\n\r\n// numberstring\r\n.create-or-alter function \r\nwith (docstring = 'Convert a number to a string', folder = 'Common')\r\nnumberstring(num: double, abbrev: bool = true)\r\n{\r\n replace_regex(case(\r\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\r\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\r\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\r\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\r\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\r\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\r\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\r\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\r\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\r\n tostring(num)\r\n ), @'\\.0$', '')\r\n}\r\n\r\n\r\n//===| Other |==========================================================================================================\r\n\r\n// ifempty\r\n.create-or-alter function \r\nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\r\nifempty(val: dynamic, defaultVal: dynamic)\r\n{\r\n iff(isempty(val), defaultVal, val)\r\n}\r\n", - "$fxv#7": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Settings |=======================================================================================================\r\n\r\n.create-merge table HubSettingsLog (\r\n version: string,\r\n scopes: dynamic,\r\n retention: dynamic\r\n)\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// HubSettings function\r\n.create-or-alter function\r\nwith (docstring='Gets the latest version of hub settings.', folder='Settings')\r\nHubSettings()\r\n{\r\n HubSettingsLog\r\n | extend timestamp = ingestion_time()\r\n | summarize arg_max(timestamp, *)\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// HubScopes function\r\n.create-or-alter function\r\nwith (docstring='Gets the currently configured scopes.', folder='Settings')\r\nHubScopes()\r\n{\r\n HubSettings\r\n | project scopes\r\n | mv-expand scopes\r\n}\r\n\r\n\r\n//===| Open data |======================================================================================================\r\n\r\n// PricingUnits -- Create table if it doesn't exist\r\n.create-merge table PricingUnits ( ignore: string )\r\n\r\n// PricingUnits -- Remove all columns\r\n.alter table PricingUnits ( ignore: string )\r\n\r\n// PricingUnits -- Redefine all columns to change types\r\n.alter table PricingUnits (\r\n x_PricingUnitDescription: string,\r\n x_PricingBlockSize: real,\r\n PricingUnit: string\r\n)\r\n\r\n// Regions\r\n.create-merge table Regions(\r\n ResourceLocation: string,\r\n RegionId: string,\r\n RegionName: string\r\n)\r\n\r\n// ResourceTypes\r\n.create-merge table ResourceTypes(\r\n x_ResourceType: string,\r\n SingularDisplayName: string,\r\n PluralDisplayName: string,\r\n LowerSingularDisplayName: string,\r\n LowerPluralDisplayName: string,\r\n IsPreview: bool,\r\n Description: string,\r\n IconUri: string\r\n)\r\n\r\n// Services\r\n.create-merge table Services(\r\n x_ConsumedService: string,\r\n x_ResourceType: string,\r\n ServiceName: string,\r\n ServiceCategory: string,\r\n ServiceSubcategory: string,\r\n PublisherName: string,\r\n x_PublisherCategory: string,\r\n x_Environment: string,\r\n x_ServiceModel: string\r\n)\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// parse_resourceid\r\n.create-or-alter function\r\nwith (docstring = 'Parses an Azure resource ID to extract resource attributes like the name, type, resource group, and subaccount ID.', folder = 'Common')\r\nparse_resourceid(resourceId: string) {\r\n let ResourceId = tolower(resourceId);\r\n // let ResourceId = tolower('/providers/Microsoft.BillingBenefits/savingsPlanOrders/2d2e284b-0638-427e-b8c6-1b874d4f17c8/sp/xxx');\r\n let SubAccountId = tostring(extract('/subscriptions/[^/]+', 1, ResourceId));\r\n let x_ResourceGroupName = tostring(extract('/resourcegroups/[^/]+', 1, ResourceId));\r\n let providerPath = iff(ResourceId !contains '/providers/', '', split(iff(ResourceId startswith '/subscriptions/', strcat('/providers/microsoft.resources/', ResourceId), ResourceId), '/providers/')[-1]);\r\n let x_ResourceProvider = iff(isempty(providerPath), '', split(providerPath, '/')[0]);\r\n let tmp_ResourceProviderPath = iff(isempty(providerPath), '', substring(providerPath, strlen(x_ResourceProvider) + 1));\r\n let segments = split(tmp_ResourceProviderPath, '/');\r\n let ResourceName = trim(@'/+', replace_string(strcat_array(array_iff(\r\n dynamic([false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true]),\r\n segments, dynamic([])), '/'), '//', '/'));\r\n let x_ResourceTypePath = trim(@'/+', replace_string(strcat_array(array_iff(\r\n dynamic([true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]),\r\n segments, dynamic([])), '/'), '//', '/'));\r\n let xRT = iff(isempty(x_ResourceProvider) or isempty(x_ResourceTypePath), '', strcat(x_ResourceProvider, '/', x_ResourceTypePath));\r\n // TODO: Remove ResourceType in 0.9\r\n bag_pack('ResourceId', ResourceId, 'ResourceName', ResourceName, 'ResourceType', xRT, 'SubAccountId', SubAccountId, 'x_ResourceGroupName', x_ResourceGroupName, 'x_ResourceProvider', x_ResourceProvider, 'x_ResourceType', xRT)\r\n}\r\n", - "$fxv#8": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| ActualCosts |====================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_raw table -- Create the table if it doesn't exist\r\n.create-merge table ActualCosts_raw ( ignore: string )\r\n\r\n// ActualCosts_raw table -- Remove all columns to allow changing column types\r\n.alter table ActualCosts_raw ( ignore: string )\r\n\r\n// ActualCosts_raw table -- Redefine all columns\r\n.alter table ActualCosts_raw (\r\n AccountName: string,\r\n AccountOwnerId: string,\r\n AdditionalInfo: string,\r\n AvailabilityZone: string,\r\n BillingAccountId: string, \r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n BillingPeriodEndDate: datetime,\r\n BillingPeriodStartDate: datetime,\r\n BillingProfileId: string,\r\n BillingProfileName: string,\r\n ChargeType: string,\r\n ConsumedService: string,\r\n CostCenter: string,\r\n Cost: real,\r\n Date: datetime,\r\n EffectivePrice: real,\r\n Frequency: string,\r\n InvoiceSection: string,\r\n InvoiceSectionId: string,\r\n IsAzureCreditEligible: bool,\r\n MeterCategory: string,\r\n MeterId: string,\r\n MeterName: string,\r\n MeterRegion: string,\r\n MeterSubCategory: string,\r\n OfferId: string,\r\n PartNumber: string,\r\n PlanName: string,\r\n Product: string,\r\n ProductOrderId: string,\r\n ProductOrderName: string,\r\n PublisherName: string,\r\n PublisherType: string,\r\n Quantity: real,\r\n ReservationId: string,\r\n ReservationName: string,\r\n ResourceGroup: string,\r\n ResourceId: string,\r\n ResourceLocation: string,\r\n ResourceName: string,\r\n ServiceFamily: string,\r\n ServiceInfo1: string,\r\n ServiceInfo2: string,\r\n SubscriptionId: string,\r\n SubscriptionName: string,\r\n Tags: string,\r\n Term: string,\r\n UnitOfMeasure: string,\r\n UnitPrice: real\r\n)\r\n\r\n// ActualCosts_raw ingestion mapping\r\n.create-or-alter table ActualCosts_raw ingestion parquet mapping \"ActualCosts_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\r\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\r\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\r\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\r\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\r\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\r\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\r\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\r\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\r\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\r\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\r\n]\r\n```\r\n\r\n// ActualCosts_raw retention policy (clear historical data)\r\n.alter-merge table ActualCosts_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// ActualCosts_raw retention policy (set the user-defined retention period)\r\n.alter-merge table ActualCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable ActualCosts_raw streaming ingestion (required for Fabric)\r\n.alter table ActualCosts_raw policy streamingingestion disable\r\n\r\n\r\n//===| AmortizedCosts |=================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_raw table -- Create the table if it doesn't exist\r\n.create-merge table AmortizedCosts_raw ( ignore: string )\r\n\r\n// AmortizedCosts_raw table -- Remove all columns to allow changing column types\r\n.alter table AmortizedCosts_raw ( ignore: string )\r\n\r\n// AmortizedCosts_raw table -- Redefine all columns\r\n.alter table AmortizedCosts_raw (\r\n AccountName: string,\r\n AccountOwnerId: string,\r\n AdditionalInfo: string,\r\n AvailabilityZone: string,\r\n BillingAccountId: string, \r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n BillingPeriodEndDate: datetime,\r\n BillingPeriodStartDate: datetime,\r\n BillingProfileId: string,\r\n BillingProfileName: string,\r\n ChargeType: string,\r\n ConsumedService: string,\r\n CostCenter: string,\r\n Cost: real,\r\n Date: datetime,\r\n EffectivePrice: real,\r\n Frequency: string,\r\n InvoiceSection: string,\r\n InvoiceSectionId: string,\r\n IsAzureCreditEligible: bool,\r\n MeterCategory: string,\r\n MeterId: string,\r\n MeterName: string,\r\n MeterRegion: string,\r\n MeterSubCategory: string,\r\n OfferId: string,\r\n PartNumber: string,\r\n PlanName: string,\r\n Product: string,\r\n ProductOrderId: string,\r\n ProductOrderName: string,\r\n PublisherName: string,\r\n PublisherType: string,\r\n Quantity: real,\r\n ReservationId: string,\r\n ReservationName: string,\r\n ResourceGroup: string,\r\n ResourceId: string,\r\n ResourceLocation: string,\r\n ResourceName: string,\r\n ServiceFamily: string,\r\n ServiceInfo1: string,\r\n ServiceInfo2: string,\r\n SubscriptionId: string,\r\n SubscriptionName: string,\r\n Tags: string,\r\n Term: string,\r\n UnitOfMeasure: string,\r\n UnitPrice: real\r\n)\r\n\r\n// AmortizedCosts_raw ingestion mapping\r\n.create-or-alter table AmortizedCosts_raw ingestion parquet mapping \"AmortizedCosts_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\r\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\r\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\r\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\r\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\r\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\r\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\r\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\r\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\r\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\r\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\r\n]\r\n```\r\n\r\n// AmortizedCosts_raw retention policy (clear historical data)\r\n.alter-merge table AmortizedCosts_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// AmortizedCosts_raw retention policy (set the user-defined retention period)\r\n.alter-merge table AmortizedCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable AmortizedCosts_raw streaming ingestion (required for Fabric)\r\n.alter table AmortizedCosts_raw policy streamingingestion disable\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_raw table -- Create the table if it doesn't exist\r\n.create-merge table CommitmentDiscountUsage_raw ( ignore: string )\r\n\r\n// CommitmentDiscountUsage_raw table -- Remove all columns to allow changing column types\r\n.alter table CommitmentDiscountUsage_raw ( ignore: string )\r\n\r\n// CommitmentDiscountUsage_raw table -- Redefine all columns\r\n.alter table CommitmentDiscountUsage_raw (\r\n InstanceFlexibilityGroup: string,\r\n InstanceFlexibilityRatio: real,\r\n InstanceId: string,\r\n Kind: string,\r\n ReservationId: string,\r\n ReservationOrderId: string,\r\n ReservedHours: real,\r\n SkuName: string,\r\n TotalReservedQuantity: real,\r\n UsageDate: datetime,\r\n UsedHours: real,\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// CommitmentDiscountUsage_raw ingestion mapping\r\n.create-or-alter table CommitmentDiscountUsage_raw ingestion parquet mapping \"CommitmentDiscountUsage_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\r\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\r\n { \"Column\": \"InstanceId\", \"Properties\": { \"Field\": \"InstanceId\" } },\r\n { \"Column\": \"Kind\", \"Properties\": { \"Field\": \"Kind\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\r\n { \"Column\": \"ReservedHours\", \"Properties\": { \"Field\": \"ReservedHours\" } },\r\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\r\n { \"Column\": \"TotalReservedQuantity\", \"Properties\": { \"Field\": \"TotalReservedQuantity\" } },\r\n { \"Column\": \"UsageDate\", \"Properties\": { \"Field\": \"UsageDate\" } },\r\n { \"Column\": \"UsedHours\", \"Properties\": { \"Field\": \"UsedHours\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// CommitmentDiscountUsage_raw retention policy (clear historical data)\r\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// CommitmentDiscountUsage_raw retention policy (set the user-defined retention period)\r\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable CommitmentDiscountUsage_raw streaming ingestion (required for Fabric)\r\n.alter table CommitmentDiscountUsage_raw policy streamingingestion disable\r\n\r\n\r\n//===| Costs |==========================================================================================================\r\n// Supported versions:\r\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n// - Tencent: 1.0 -- See https://www.tencentcloud.com/document/product/555/67495 / https://www.tencentcloud.com/document/product/555/67496\r\n// - Alibaba: 1.0 -- See https://www.alibabacloud.com/help/en/user-center/user-guide/export-alibaba-cloud-standard-billing-focus\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_raw table -- Create the table if it doesn't exist\r\n.create-merge table Costs_raw ( ignore: string )\r\n\r\n// Costs_raw table -- Remove all columns to allow changing column types\r\n.alter table Costs_raw ( ignore: string )\r\n\r\n// Costs_raw table -- Redefine all columns\r\n.alter table Costs_raw (\r\n AvailabilityZone: string, // FOCUS 0.5+\r\n BilledCost: real, // FOCUS 0.5+\r\n BillingAccountId: string, // FOCUS 0.5+\r\n BillingAccountName: string, // FOCUS 0.5+\r\n BillingAccountType: string, // Azure 1.0-preview(v1)+\r\n BillingCurrency: string, // FOCUS 0.5+\r\n BillingPeriodEnd: datetime, // FOCUS 0.5+\r\n BillingPeriodStart: datetime, // FOCUS 0.5+\r\n CapacityReservationId: string, // FOCUS 1.1+\r\n CapacityReservationStatus: string, // FOCUS 1.1+\r\n ChargeCategory: string, // FOCUS 1.0-preview+\r\n ChargeClass: string, // FOCUS 1.0+\r\n ChargeDescription: string, // FOCUS 1.0+\r\n ChargeFrequency: string, // FOCUS 1.0+\r\n ChargePeriodEnd: datetime, // FOCUS 0.5+\r\n ChargePeriodStart: datetime, // FOCUS 0.5+\r\n ChargeSubcategory: string, // FOCUS 1.0-preview only\r\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountId: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountName: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountQuantity: real, // FOCUS 1.1+\r\n CommitmentDiscountStatus: string, // FOCUS 1.0+\r\n CommitmentDiscountType: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountUnit: string, // FOCUS 1.1+\r\n ConsumedQuantity: real, // FOCUS 1.0+\r\n ConsumedUnit: string, // FOCUS 1.0+\r\n ContractedCost: real, // FOCUS 1.0+\r\n ContractedUnitPrice: real, // FOCUS 1.0+\r\n EffectiveCost: real, // FOCUS 1.0-preview+\r\n InvoiceId: string, // FOCUS 1.2+\r\n InvoiceIssuerName: string, // FOCUS 0.5+\r\n ListCost: real, // FOCUS 1.0-preview+\r\n ListUnitPrice: real, // FOCUS 1.0-preview+\r\n PricingCategory: string, // FOCUS 1.0-preview+\r\n PricingCurrency: string, // FOCUS 1.2+\r\n PricingQuantity: real, // FOCUS 1.0-preview+\r\n PricingUnit: string, // FOCUS 1.0-preview+\r\n ProviderName: string, // FOCUS 0.5+\r\n PublisherName: string, // FOCUS 0.5+\r\n Region: string, // FOCUS 0.5-1.0-preview (deprecated)\r\n RegionId: string, // FOCUS 1.0+\r\n RegionName: string, // FOCUS 1.0+\r\n ResourceId: string, // FOCUS 0.5+\r\n ResourceName: string, // FOCUS 0.5+\r\n ResourceType: string, // FOCUS 1.0-preview+\r\n ServiceCategory: string, // FOCUS 0.5+\r\n ServiceName: string, // FOCUS 0.5+\r\n ServiceSubcategory: string, // FOCUS 1.1+\r\n SkuId: string, // FOCUS 1.0-preview+\r\n SkuMeter: string, // FOCUS 1.1+\r\n SkuPriceDetails: string, // FOCUS 1.1+\r\n SkuPriceId: string, // FOCUS 1.0-preview+\r\n SubAccountId: string, // FOCUS 0.5+\r\n SubAccountName: string, // FOCUS 0.5+\r\n SubAccountType: string, // Azure 1.0-preview(v1)+\r\n Tags: string, // FOCUS 1.0-preview+\r\n UsageAmount: real, // GCP Jan 2024 -- Removed Mar 2024 (UsageQuantity)\r\n UsageQuantity: real, // FOCUS 1.0-preview only\r\n UsageUnit: string, // FOCUS 1.0-preview only\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_AmortizationClass: string, // Azure 1.2-preview+\r\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingItemCode: string, // Alibaba 1.0+\r\n x_BillingItemName: string, // Alibaba 1.0+\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_CommodityCode: string, // Alibaba 1.0+\r\n x_CommodityName: string, // Alibaba 1.0+\r\n x_ComponentName: string, // Tencent 1.0+\r\n x_ComponentType: string, // Tencent 1.0+\r\n x_ContractedCostInUsd: real, // Azure 1.0+\r\n x_Cost: real, // GCP Jan 2024 -- Removed Jun 2024 (ContractedCost)\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: string, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_CostType: string, // GCP Jan 2024\r\n x_Credits: string, // GCP Jan 2024\r\n x_CurrencyConversionRate: real, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: string, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0+\r\n x_InstanceID: string, // Alibaba 1.0+\r\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_OnDemandCost: real, // Azure 1.0-preview(v1) only\r\n x_OnDemandCostInUsd: real, // Azure 1.0-preview(v1) only\r\n x_OnDemandUnitPrice: real, // Azure 1.0-preview(v1) only\r\n x_Operation: string, // AWS 1.0\r\n x_OwnerAccountID: string, // Tencent 1.0+\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency: string, // Azure 1.0-preview(v1)-1.0r2\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServiceModel: string, // Azure 1.2-preview+\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: string, // Azure 1.0-preview(v1)-1.2-preview\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName: string, // Azure 1.0-preview(v1)-1.0r2\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuPlanName: string, // Azure 1.2-preview+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string, // Hubs v1_0+\r\n x_SubproductName: string, // Tencent 1.0+ // cSpell:ignore Subproduct\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Costs_raw ingestion mapping\r\n.create-or-alter table Costs_raw ingestion parquet mapping \"Costs_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BilledCost\", \"Properties\": { \"Field\": \"BilledCost\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingAccountType\", \"Properties\": { \"Field\": \"BillingAccountType\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEnd\", \"Properties\": { \"Field\": \"BillingPeriodEnd\" } },\r\n { \"Column\": \"BillingPeriodStart\", \"Properties\": { \"Field\": \"BillingPeriodStart\" } },\r\n { \"Column\": \"CapacityReservationId\", \"Properties\": { \"Field\": \"CapacityReservationId\" } },\r\n { \"Column\": \"CapacityReservationStatus\", \"Properties\": { \"Field\": \"CapacityReservationStatus\" } },\r\n { \"Column\": \"ChargeCategory\", \"Properties\": { \"Field\": \"ChargeCategory\" } },\r\n { \"Column\": \"ChargeClass\", \"Properties\": { \"Field\": \"ChargeClass\" } },\r\n { \"Column\": \"ChargeDescription\", \"Properties\": { \"Field\": \"ChargeDescription\" } },\r\n { \"Column\": \"ChargeFrequency\", \"Properties\": { \"Field\": \"ChargeFrequency\" } },\r\n { \"Column\": \"ChargePeriodEnd\", \"Properties\": { \"Field\": \"ChargePeriodEnd\" } },\r\n { \"Column\": \"ChargePeriodStart\", \"Properties\": { \"Field\": \"ChargePeriodStart\" } },\r\n { \"Column\": \"ChargeSubcategory\", \"Properties\": { \"Field\": \"ChargeSubcategory\" } },\r\n { \"Column\": \"CommitmentDiscountCategory\", \"Properties\": { \"Field\": \"CommitmentDiscountCategory\" } },\r\n { \"Column\": \"CommitmentDiscountId\", \"Properties\": { \"Field\": \"CommitmentDiscountId\" } },\r\n { \"Column\": \"CommitmentDiscountName\", \"Properties\": { \"Field\": \"CommitmentDiscountName\" } },\r\n { \"Column\": \"CommitmentDiscountQuantity\", \"Properties\": { \"Field\": \"CommitmentDiscountQuantity\" } },\r\n { \"Column\": \"CommitmentDiscountStatus\", \"Properties\": { \"Field\": \"CommitmentDiscountStatus\" } },\r\n { \"Column\": \"CommitmentDiscountType\", \"Properties\": { \"Field\": \"CommitmentDiscountType\" } },\r\n { \"Column\": \"CommitmentDiscountUnit\", \"Properties\": { \"Field\": \"CommitmentDiscountUnit\" } },\r\n { \"Column\": \"ConsumedQuantity\", \"Properties\": { \"Field\": \"ConsumedQuantity\" } },\r\n { \"Column\": \"ConsumedUnit\", \"Properties\": { \"Field\": \"ConsumedUnit\" } },\r\n { \"Column\": \"ContractedCost\", \"Properties\": { \"Field\": \"ContractedCost\" } },\r\n { \"Column\": \"ContractedUnitPrice\", \"Properties\": { \"Field\": \"ContractedUnitPrice\" } },\r\n { \"Column\": \"EffectiveCost\", \"Properties\": { \"Field\": \"EffectiveCost\" } },\r\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\r\n { \"Column\": \"InvoiceIssuerName\", \"Properties\": { \"Field\": \"InvoiceIssuerName\" } },\r\n { \"Column\": \"ListCost\", \"Properties\": { \"Field\": \"ListCost\" } },\r\n { \"Column\": \"ListUnitPrice\", \"Properties\": { \"Field\": \"ListUnitPrice\" } },\r\n { \"Column\": \"PricingCategory\", \"Properties\": { \"Field\": \"PricingCategory\" } },\r\n { \"Column\": \"PricingCurrency\", \"Properties\": { \"Field\": \"PricingCurrency\" } },\r\n { \"Column\": \"PricingQuantity\", \"Properties\": { \"Field\": \"PricingQuantity\" } },\r\n { \"Column\": \"PricingUnit\", \"Properties\": { \"Field\": \"PricingUnit\" } },\r\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\r\n { \"Column\": \"RegionId\", \"Properties\": { \"Field\": \"RegionId\" } },\r\n { \"Column\": \"RegionName\", \"Properties\": { \"Field\": \"RegionName\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\r\n { \"Column\": \"ServiceCategory\", \"Properties\": { \"Field\": \"ServiceCategory\" } },\r\n { \"Column\": \"ServiceName\", \"Properties\": { \"Field\": \"ServiceName\" } },\r\n { \"Column\": \"ServiceSubcategory\", \"Properties\": { \"Field\": \"ServiceSubcategory\" } },\r\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\r\n { \"Column\": \"SkuMeter\", \"Properties\": { \"Field\": \"SkuMeter\" } },\r\n { \"Column\": \"SkuPriceDetails\", \"Properties\": { \"Field\": \"SkuPriceDetails\" } },\r\n { \"Column\": \"SkuPriceId\", \"Properties\": { \"Field\": \"SkuPriceId\" } },\r\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\r\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\r\n { \"Column\": \"SubAccountType\", \"Properties\": { \"Field\": \"SubAccountType\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"UsageAmount\", \"Properties\": { \"Field\": \"UsageAmount\" } },\r\n { \"Column\": \"UsageQuantity\", \"Properties\": { \"Field\": \"UsageQuantity\" } },\r\n { \"Column\": \"UsageUnit\", \"Properties\": { \"Field\": \"UsageUnit\" } },\r\n { \"Column\": \"x_AccountId\", \"Properties\": { \"Field\": \"x_AccountId\" } },\r\n { \"Column\": \"x_AccountName\", \"Properties\": { \"Field\": \"x_AccountName\" } },\r\n { \"Column\": \"x_AccountOwnerId\", \"Properties\": { \"Field\": \"x_AccountOwnerId\" } },\r\n { \"Column\": \"x_AmortizationClass\", \"Properties\": { \"Field\": \"x_AmortizationClass\" } },\r\n { \"Column\": \"x_BilledCostInUsd\", \"Properties\": { \"Field\": \"x_BilledCostInUsd\" } },\r\n { \"Column\": \"x_BilledUnitPrice\", \"Properties\": { \"Field\": \"x_BilledUnitPrice\" } },\r\n { \"Column\": \"x_BillingAccountId\", \"Properties\": { \"Field\": \"x_BillingAccountId\" } },\r\n { \"Column\": \"x_BillingAccountName\", \"Properties\": { \"Field\": \"x_BillingAccountName\" } },\r\n { \"Column\": \"x_BillingExchangeRate\", \"Properties\": { \"Field\": \"x_BillingExchangeRate\" } },\r\n { \"Column\": \"x_BillingExchangeRateDate\", \"Properties\": { \"Field\": \"x_BillingExchangeRateDate\" } },\r\n { \"Column\": \"x_BillingItemCode\", \"Properties\": { \"Field\": \"x_BillingItemCode\" } },\r\n { \"Column\": \"x_BillingItemName\", \"Properties\": { \"Field\": \"x_BillingItemName\" } },\r\n { \"Column\": \"x_BillingProfileId\", \"Properties\": { \"Field\": \"x_BillingProfileId\" } },\r\n { \"Column\": \"x_BillingProfileName\", \"Properties\": { \"Field\": \"x_BillingProfileName\" } },\r\n { \"Column\": \"x_ChargeId\", \"Properties\": { \"Field\": \"x_ChargeId\" } },\r\n { \"Column\": \"x_ContractedCostInUsd\", \"Properties\": { \"Field\": \"x_ContractedCostInUsd\" } },\r\n { \"Column\": \"x_CommodityCode\", \"Properties\": { \"Field\": \"x_CommodityCode\" } },\r\n { \"Column\": \"x_CommodityName\", \"Properties\": { \"Field\": \"x_CommodityName\" } },\r\n { \"Column\": \"x_ComponentName\", \"Properties\": { \"Field\": \"x_ComponentName\" } },\r\n { \"Column\": \"x_ComponentType\", \"Properties\": { \"Field\": \"x_ComponentType\" } },\r\n { \"Column\": \"x_Cost\", \"Properties\": { \"Field\": \"x_Cost\" } },\r\n { \"Column\": \"x_CostAllocationRuleName\", \"Properties\": { \"Field\": \"x_CostAllocationRuleName\" } },\r\n { \"Column\": \"x_CostCategories\", \"Properties\": { \"Field\": \"x_CostCategories\" } },\r\n { \"Column\": \"x_CostCenter\", \"Properties\": { \"Field\": \"x_CostCenter\" } },\r\n { \"Column\": \"x_Credits\", \"Properties\": { \"Field\": \"x_Credits\" } },\r\n { \"Column\": \"x_CostType\", \"Properties\": { \"Field\": \"x_CostType\" } },\r\n { \"Column\": \"x_CurrencyConversionRate\", \"Properties\": { \"Field\": \"x_CurrencyConversionRate\" } },\r\n { \"Column\": \"x_CustomerId\", \"Properties\": { \"Field\": \"x_CustomerId\" } },\r\n { \"Column\": \"x_CustomerName\", \"Properties\": { \"Field\": \"x_CustomerName\" } },\r\n { \"Column\": \"x_Discount\", \"Properties\": { \"Field\": \"x_Discount\" } },\r\n { \"Column\": \"x_EffectiveCostInUsd\", \"Properties\": { \"Field\": \"x_EffectiveCostInUsd\" } },\r\n { \"Column\": \"x_EffectiveUnitPrice\", \"Properties\": { \"Field\": \"x_EffectiveUnitPrice\" } },\r\n { \"Column\": \"x_ExportTime\", \"Properties\": { \"Field\": \"x_ExportTime\" } },\r\n { \"Column\": \"x_InstanceID\", \"Properties\": { \"Field\": \"x_InstanceID\" } },\r\n { \"Column\": \"x_InvoiceId\", \"Properties\": { \"Field\": \"x_InvoiceId\" } },\r\n { \"Column\": \"x_InvoiceIssuerId\", \"Properties\": { \"Field\": \"x_InvoiceIssuerId\" } },\r\n { \"Column\": \"x_InvoiceSectionId\", \"Properties\": { \"Field\": \"x_InvoiceSectionId\" } },\r\n { \"Column\": \"x_InvoiceSectionName\", \"Properties\": { \"Field\": \"x_InvoiceSectionName\" } },\r\n { \"Column\": \"x_ListCostInUsd\", \"Properties\": { \"Field\": \"x_ListCostInUsd\" } },\r\n { \"Column\": \"x_Location\", \"Properties\": { \"Field\": \"x_Location\" } },\r\n { \"Column\": \"x_OnDemandCost\", \"Properties\": { \"Field\": \"x_OnDemandCost\" } },\r\n { \"Column\": \"x_OnDemandCostInUsd\", \"Properties\": { \"Field\": \"x_OnDemandCostInUsd\" } },\r\n { \"Column\": \"x_OnDemandUnitPrice\", \"Properties\": { \"Field\": \"x_OnDemandUnitPrice\" } },\r\n { \"Column\": \"x_Operation\", \"Properties\": { \"Field\": \"x_Operation\" } },\r\n { \"Column\": \"x_OwnerAccountID\", \"Properties\": { \"Field\": \"x_OwnerAccountID\" } },\r\n { \"Column\": \"x_PartnerCreditApplied\", \"Properties\": { \"Field\": \"x_PartnerCreditApplied\" } },\r\n { \"Column\": \"x_PartnerCreditRate\", \"Properties\": { \"Field\": \"x_PartnerCreditRate\" } },\r\n { \"Column\": \"x_PricingBlockSize\", \"Properties\": { \"Field\": \"x_PricingBlockSize\" } },\r\n { \"Column\": \"x_PricingCurrency\", \"Properties\": { \"Field\": \"x_PricingCurrency\" } },\r\n { \"Column\": \"x_PricingSubcategory\", \"Properties\": { \"Field\": \"x_PricingSubcategory\" } },\r\n { \"Column\": \"x_PricingUnitDescription\", \"Properties\": { \"Field\": \"x_PricingUnitDescription\" } },\r\n { \"Column\": \"x_Project\", \"Properties\": { \"Field\": \"x_Project\" } },\r\n { \"Column\": \"x_PublisherCategory\", \"Properties\": { \"Field\": \"x_PublisherCategory\" } },\r\n { \"Column\": \"x_PublisherId\", \"Properties\": { \"Field\": \"x_PublisherId\" } },\r\n { \"Column\": \"x_ResellerId\", \"Properties\": { \"Field\": \"x_ResellerId\" } },\r\n { \"Column\": \"x_ResellerName\", \"Properties\": { \"Field\": \"x_ResellerName\" } },\r\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\r\n { \"Column\": \"x_ResourceType\", \"Properties\": { \"Field\": \"x_ResourceType\" } },\r\n { \"Column\": \"x_ServiceCode\", \"Properties\": { \"Field\": \"x_ServiceCode\" } },\r\n { \"Column\": \"x_ServiceId\", \"Properties\": { \"Field\": \"x_ServiceId\" } },\r\n { \"Column\": \"x_ServiceModel\", \"Properties\": { \"Field\": \"x_ServiceModel\" } },\r\n { \"Column\": \"x_ServicePeriodEnd\", \"Properties\": { \"Field\": \"x_ServicePeriodEnd\" } },\r\n { \"Column\": \"x_ServicePeriodStart\", \"Properties\": { \"Field\": \"x_ServicePeriodStart\" } },\r\n { \"Column\": \"x_SkuDescription\", \"Properties\": { \"Field\": \"x_SkuDescription\" } },\r\n { \"Column\": \"x_SkuDetails\", \"Properties\": { \"Field\": \"x_SkuDetails\" } },\r\n { \"Column\": \"x_SkuIsCreditEligible\", \"Properties\": { \"Field\": \"x_SkuIsCreditEligible\" } },\r\n { \"Column\": \"x_SkuMeterCategory\", \"Properties\": { \"Field\": \"x_SkuMeterCategory\" } },\r\n { \"Column\": \"x_SkuMeterId\", \"Properties\": { \"Field\": \"x_SkuMeterId\" } },\r\n { \"Column\": \"x_SkuMeterName\", \"Properties\": { \"Field\": \"x_SkuMeterName\" } },\r\n { \"Column\": \"x_SkuMeterSubcategory\", \"Properties\": { \"Field\": \"x_SkuMeterSubcategory\" } },\r\n { \"Column\": \"x_SkuOfferId\", \"Properties\": { \"Field\": \"x_SkuOfferId\" } },\r\n { \"Column\": \"x_SkuOrderId\", \"Properties\": { \"Field\": \"x_SkuOrderId\" } },\r\n { \"Column\": \"x_SkuOrderName\", \"Properties\": { \"Field\": \"x_SkuOrderName\" } },\r\n { \"Column\": \"x_SkuPartNumber\", \"Properties\": { \"Field\": \"x_SkuPartNumber\" } },\r\n { \"Column\": \"x_SkuPlanName\", \"Properties\": { \"Field\": \"x_SkuPlanName\" } },\r\n { \"Column\": \"x_SkuRegion\", \"Properties\": { \"Field\": \"x_SkuRegion\" } },\r\n { \"Column\": \"x_SkuServiceFamily\", \"Properties\": { \"Field\": \"x_SkuServiceFamily\" } },\r\n { \"Column\": \"x_SkuTerm\", \"Properties\": { \"Field\": \"x_SkuTerm\" } },\r\n { \"Column\": \"x_SkuTier\", \"Properties\": { \"Field\": \"x_SkuTier\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } },\r\n { \"Column\": \"x_SubproductName\", \"Properties\": { \"Field\": \"x_SubproductName\" } },\r\n { \"Column\": \"x_UsageType\", \"Properties\": { \"Field\": \"x_UsageType\" } }\r\n]\r\n```\r\n\r\n// Costs_raw retention policy (clear historical data)\r\n.alter-merge table Costs_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Costs_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Costs_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Costs_raw streaming ingestion (required for Fabric)\r\n.alter table Costs_raw policy streamingingestion disable\r\n\r\n\r\n//===| Prices |=========================================================================================================\r\n// NOTE: Must be before cost details.\r\n//\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_raw table -- Create the table if it doesn't exist\r\n.create-merge table Prices_raw ( ignore: string )\r\n\r\n// Prices_raw table -- Remove all columns to allow changing column types\r\n.alter table Prices_raw ( ignore: string )\r\n\r\n// Prices_raw table -- Redefine all columns\r\n.alter table Prices_raw (\r\n BasePrice: real, // Azure EA + MCA\r\n BillingAccountId: string, // Azure MCA\r\n BillingAccountName: string, // Azure MCA\r\n BillingCurrency: string, // Azure MCA\r\n BillingProfileId: string, // Azure MCA\r\n BillingProfileName: string, // Azure MCA\r\n Currency: string, // Azure MCA\r\n CurrencyCode: string, // Azure EA\r\n EffectiveEndDate: datetime, // Azure MCA\r\n EffectiveStartDate: datetime, // Azure EA + MCA\r\n EnrollmentNumber: string, // Azure EA\r\n IncludedQuantity: real, // Azure EA\r\n MarketPrice: real, // Azure EA + MCA\r\n MeterCategory: string, // Azure EA + MCA\r\n MeterId: string, // Azure MCA\r\n MeterID: string, // Azure EA\r\n MeterName: string, // Azure EA + MCA\r\n MeterRegion: string, // Azure EA + MCA\r\n MeterSubCategory: string, // Azure EA + MCA\r\n MeterType: string, // Azure EA + MCA\r\n OfferID: string, // Azure EA\r\n PartNumber: string, // Azure EA\r\n PriceType: string, // Azure EA + MCA\r\n Product: string, // Azure EA + MCA\r\n ProductId: string, // Azure MCA\r\n ProductID: string, // Azure EA\r\n ServiceFamily: string, // Azure EA + MCA\r\n SkuId: string, // Azure MCA\r\n SkuID: string, // Azure EA\r\n Term: string, // Azure EA + MCA\r\n TierMinimumUnits: real, // Azure MCA\r\n UnitOfMeasure: string, // Azure EA + MCA\r\n UnitPrice: real, // Azure EA + MCA\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Prices_raw ingestion mapping\r\n.create-or-alter table Prices_raw ingestion parquet mapping \"Prices_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"BasePrice\", \"Properties\": { \"Field\": \"BasePrice\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\r\n { \"Column\": \"CurrencyCode\", \"Properties\": { \"Field\": \"CurrencyCode\" } },\r\n { \"Column\": \"EffectiveEndDate\", \"Properties\": { \"Field\": \"EffectiveEndDate\" } },\r\n { \"Column\": \"EffectiveStartDate\", \"Properties\": { \"Field\": \"EffectiveStartDate\" } },\r\n { \"Column\": \"EnrollmentNumber\", \"Properties\": { \"Field\": \"EnrollmentNumber\" } },\r\n { \"Column\": \"IncludedQuantity\", \"Properties\": { \"Field\": \"IncludedQuantity\" } },\r\n { \"Column\": \"MarketPrice\", \"Properties\": { \"Field\": \"MarketPrice\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterID\", \"Properties\": { \"Field\": \"MeterID\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"MeterType\", \"Properties\": { \"Field\": \"MeterType\" } },\r\n { \"Column\": \"OfferID\", \"Properties\": { \"Field\": \"OfferID\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PriceType\", \"Properties\": { \"Field\": \"PriceType\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductId\", \"Properties\": { \"Field\": \"ProductId\" } },\r\n { \"Column\": \"ProductID\", \"Properties\": { \"Field\": \"ProductID\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\r\n { \"Column\": \"SkuID\", \"Properties\": { \"Field\": \"SkuID\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"TierMinimumUnits\", \"Properties\": { \"Field\": \"TierMinimumUnits\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Prices_raw retention policy (clear historical data)\r\n.alter-merge table Prices_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Prices_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Prices_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Prices_raw streaming ingestion (required for Fabric)\r\n.alter table Prices_raw policy streamingingestion disable\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_raw table -- Create the table if it doesn't exist\r\n.create-merge table Recommendations_raw ( ignore: string )\r\n\r\n// Recommendations_raw table -- Remove all columns to allow changing column types\r\n.alter table Recommendations_raw ( ignore: string )\r\n\r\n// Recommendations_raw table -- Redefine all columns\r\n.alter table Recommendations_raw (\r\n CostWithNoReservedInstances: real, // MS CM EA resv reco 2024-05-01\r\n CostWithNoReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n FirstUsageDate: datetime, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n InstanceFlexibilityGroup: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n InstanceFlexibilityRatio: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n Location: string, // MS CM EA+MCA resv reco 2024-05-01\r\n LookBackPeriod: string, // MS CM EA+MCA resv reco 2024-05-01\r\n MeterId: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n NetSavings: real, // MS CM EA resv reco 2024-05-01\r\n NetSavingsJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n NormalizedSize: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n ProviderName: string, // Hubs v1_2\r\n RecommendedQuantity: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n RecommendedQuantityNormalized: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n ResourceId: string, // Hubs v1_2\r\n ResourceName: string, // Hubs v1_2\r\n ResourceType: string, // Hubs v1_2, MS CM EA+MCA resv reco 2024-05-01\r\n Scope: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n SKU: string, // MS CM EA resv reco 2024-05-01\r\n SkuName: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces\r\n SkuProperties: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n SubAccountId: string, // Hubs v1_2\r\n SubAccountName: string, // Hubs v1_2\r\n SubscriptionId: string, // MS CM EA+MCA resv reco 2024-05-01\r\n Term: string, // MS CM EA+MCA resv reco 2024-05-01\r\n TotalCostWithReservedInstances: real, // MS CM EA resv reco 2024-05-01\r\n TotalCostWithReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n x_EffectiveCostAfter: real, // Hubs v1_2\r\n x_EffectiveCostBefore: real, // Hubs v1_2\r\n x_EffectiveCostSavings: real, // Hubs v1_2\r\n x_RecommendationCategory: string, // Hubs v1_2\r\n x_RecommendationDate: datetime, // Hubs v1_2\r\n x_RecommendationDescription: string, // Hubs v1_2\r\n x_RecommendationDetails: dynamic, // Hubs v1_2\r\n x_RecommendationId: string, // Hubs v1_2\r\n x_ResourceGroupName: string, // Hubs v1_2\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Recommendations_raw ingestion mapping\r\n.create-or-alter table Recommendations_raw ingestion parquet mapping \"Recommendations_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"CostWithNoReservedInstances\", \"Properties\": { \"Field\": \"CostWithNoReservedInstances\" } },\r\n { \"Column\": \"CostWithNoReservedInstancesJson\", \"Properties\": { \"Field\": \"CostWithNoReservedInstancesJson\" } },\r\n { \"Column\": \"FirstUsageDate\", \"Properties\": { \"Field\": \"FirstUsageDate\" } },\r\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\r\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\r\n { \"Column\": \"Location\", \"Properties\": { \"Field\": \"Location\" } },\r\n { \"Column\": \"LookBackPeriod\", \"Properties\": { \"Field\": \"LookBackPeriod\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"NetSavings\", \"Properties\": { \"Field\": \"NetSavings\" } },\r\n { \"Column\": \"NetSavingsJson\", \"Properties\": { \"Field\": \"NetSavingsJson\" } },\r\n { \"Column\": \"NormalizedSize\", \"Properties\": { \"Field\": \"NormalizedSize\" } },\r\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\r\n { \"Column\": \"RecommendedQuantity\", \"Properties\": { \"Field\": \"RecommendedQuantity\" } },\r\n { \"Column\": \"RecommendedQuantityNormalized\", \"Properties\": { \"Field\": \"RecommendedQuantityNormalized\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\r\n { \"Column\": \"Scope\", \"Properties\": { \"Field\": \"Scope\" } },\r\n { \"Column\": \"SKU\", \"Properties\": { \"Field\": \"SKU\" } },\r\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\r\n { \"Column\": \"SkuProperties\", \"Properties\": { \"Field\": \"SkuProperties\" } },\r\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\r\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"TotalCostWithReservedInstances\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstances\" } },\r\n { \"Column\": \"TotalCostWithReservedInstancesJson\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstancesJson\" } },\r\n { \"Column\": \"x_EffectiveCostAfter\", \"Properties\": { \"Field\": \"x_EffectiveCostAfter\" } },\r\n { \"Column\": \"x_EffectiveCostBefore\", \"Properties\": { \"Field\": \"x_EffectiveCostBefore\" } },\r\n { \"Column\": \"x_EffectiveCostSavings\", \"Properties\": { \"Field\": \"x_EffectiveCostSavings\" } },\r\n { \"Column\": \"x_RecommendationCategory\", \"Properties\": { \"Field\": \"x_RecommendationCategory\" } },\r\n { \"Column\": \"x_RecommendationDate\", \"Properties\": { \"Field\": \"x_RecommendationDate\" } },\r\n { \"Column\": \"x_RecommendationDescription\", \"Properties\": { \"Field\": \"x_RecommendationDescription\" } },\r\n { \"Column\": \"x_RecommendationDetails\", \"Properties\": { \"Field\": \"x_RecommendationDetails\" } },\r\n { \"Column\": \"x_RecommendationId\", \"Properties\": { \"Field\": \"x_RecommendationId\" } },\r\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Recommendations_raw retention policy (clear historical data)\r\n.alter-merge table Recommendations_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Recommendations_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Recommendations_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Recommendations_raw streaming ingestion (required for Fabric)\r\n.alter table Recommendations_raw policy streamingingestion disable\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_raw table -- Create the table if it doesn't exist\r\n.create-merge table Transactions_raw ( ignore: string )\r\n\r\n// Transactions_raw table -- Remove all columns to allow changing column types\r\n.alter table Transactions_raw ( ignore: string )\r\n\r\n// Transactions_raw table -- Redefine all columns\r\n.alter table Transactions_raw (\r\n AccountName: string, // MS CM EA resv trans 2023-05-01\r\n AccountOwnerEmail: string, // MS CM EA resv trans 2023-05-01\r\n Amount: real, // MS CM EA+MCA resv trans 2023-05-01\r\n ArmSkuName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n BillingFrequency: string, // MS CM EA+MCA resv trans 2023-05-01\r\n BillingMonth: string, // MS CM EA resv trans 2023-05-01\r\n BillingProfileId: string, // MS CM MCA resv trans 2023-05-01\r\n BillingProfileName: string, // MS CM MCA resv trans 2023-05-01\r\n CostCenter: string, // MS CM EA resv trans 2023-05-01\r\n Currency: string, // MS CM EA+MCA resv trans 2023-05-01\r\n CurrentEnrollmentId: string, // MS CM EA resv trans 2023-05-01\r\n DepartmentName: string, // MS CM EA resv trans 2023-05-01\r\n Description: string, // MS CM EA+MCA resv trans 2023-05-01\r\n EventDate: datetime, // MS CM EA+MCA resv trans 2023-05-01\r\n EventType: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Invoice: string, // MS CM EA+MCA resv trans 2023-05-01\r\n InvoiceId: string, // MS CM EA+MCA resv trans 2023-05-01\r\n InvoiceSectionId: string, // MS CM MCA resv trans 2023-05-01\r\n InvoiceSectionName: string, // MS CM MCA resv trans 2023-05-01\r\n MonetaryCommitment: real, // MS CM EA resv trans 2023-05-01\r\n Overage: real, // MS CM EA resv trans 2023-05-01\r\n PurchasingEnrollment: string, // MS CM EA resv trans 2023-05-01\r\n PurchasingSubscriptionGuid: string, // MS CM EA+MCA resv trans 2023-05-01\r\n PurchasingSubscriptionName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Quantity: real, // MS CM EA+MCA resv trans 2023-05-01\r\n Region: string, // MS CM EA+MCA resv trans 2023-05-01\r\n ReservationOrderId: string, // MS CM EA+MCA resv trans 2023-05-01\r\n ReservationOrderName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Term: string, // MS CM EA+MCA resv trans 2023-05-01\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Transactions_raw ingestion mapping\r\n.create-or-alter table Transactions_raw ingestion parquet mapping \"Transactions_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerEmail\", \"Properties\": { \"Field\": \"AccountOwnerEmail\" } },\r\n { \"Column\": \"Amount\", \"Properties\": { \"Field\": \"Amount\" } },\r\n { \"Column\": \"ArmSkuName\", \"Properties\": { \"Field\": \"ArmSkuName\" } },\r\n { \"Column\": \"BillingFrequency\", \"Properties\": { \"Field\": \"BillingFrequency\" } },\r\n { \"Column\": \"BillingMonth\", \"Properties\": { \"Field\": \"BillingMonth\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\r\n { \"Column\": \"CurrentEnrollmentId\", \"Properties\": { \"Field\": \"CurrentEnrollmentId\" } },\r\n { \"Column\": \"DepartmentName\", \"Properties\": { \"Field\": \"DepartmentName\" } },\r\n { \"Column\": \"Description\", \"Properties\": { \"Field\": \"Description\" } },\r\n { \"Column\": \"EventDate\", \"Properties\": { \"Field\": \"EventDate\" } },\r\n { \"Column\": \"EventType\", \"Properties\": { \"Field\": \"EventType\" } },\r\n { \"Column\": \"Invoice\", \"Properties\": { \"Field\": \"Invoice\" } },\r\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"InvoiceSectionName\", \"Properties\": { \"Field\": \"InvoiceSectionName\" } },\r\n { \"Column\": \"MonetaryCommitment\", \"Properties\": { \"Field\": \"MonetaryCommitment\" } },\r\n { \"Column\": \"Overage\", \"Properties\": { \"Field\": \"Overage\" } },\r\n { \"Column\": \"PurchasingEnrollment\", \"Properties\": { \"Field\": \"PurchasingEnrollment\" } },\r\n { \"Column\": \"PurchasingSubscriptionGuid\", \"Properties\": { \"Field\": \"PurchasingSubscriptionGuid\" } },\r\n { \"Column\": \"PurchasingSubscriptionName\", \"Properties\": { \"Field\": \"PurchasingSubscriptionName\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\r\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\r\n { \"Column\": \"ReservationOrderName\", \"Properties\": { \"Field\": \"ReservationOrderName\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Transactions_raw retention policy (clear historical data)\r\n.alter-merge table Transactions_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Transactions_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Transactions_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Transactions_raw streaming ingestion (required for Fabric)\r\n.alter table Transactions_raw policy streamingingestion disable\r\n\r\n", - "$fxv#9": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Prices |=========================================================================================================\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All prices transformed to FOCUS 1.0. Use Prices_transform_v1_2() instead.', folder='Prices')\r\nPrices_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n let prices = materialize(\r\n Prices_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n BasePrice = todecimal(BasePrice),\r\n IncludedQuantity = todecimal(IncludedQuantity),\r\n MarketPrice = todecimal(MarketPrice),\r\n TierMinimumUnits = todecimal(TierMinimumUnits),\r\n UnitPrice = todecimal(UnitPrice)\r\n //\r\n | extend x_SkuId = coalesce(SkuId, SkuID)\r\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\r\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\r\n | extend x_SkuTerm = isoMonths(Term)\r\n | project-rename\r\n x_BaseUnitPrice = BasePrice,\r\n x_EffectivePeriodEnd = EffectiveEndDate,\r\n x_EffectivePeriodStart = EffectiveStartDate,\r\n x_PricingUnitDescription = UnitOfMeasure,\r\n x_SkuIncludedQuantity = IncludedQuantity,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuMeterType = MeterType,\r\n x_SkuOfferId = OfferID,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPriceType = PriceType,\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTier = TierMinimumUnits\r\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, todecimal('')) // UnitPrice for savings plan is not the on-demand unit price\r\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, todecimal('')) // MarketPrice for savings plan is not the list price\r\n | extend ChargeCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Usage',\r\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\r\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\r\n ''\r\n )\r\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\r\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\r\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\r\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\r\n //\r\n // Get latest ingested row based on the unique ID\r\n | extend x_IngestionTime = ingestion_time()\r\n );\r\n //\r\n // Meters for reservations and savings plans to identify commitment eligibility\r\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\r\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\r\n //\r\n // Copy list/base/contracted prices from on-demand SKUs\r\n prices\r\n | where x_SkuPriceType == 'SavingsPlan'\r\n // If we use join, specify the shuffle key\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\r\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\r\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\r\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\r\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\r\n //\r\n // Calculate commitment discount elgibility\r\n // TODO: Would a join be faster?\r\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\r\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\r\n //\r\n // Add PricingUnit and x_PricingBlockSize\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\r\n | lookup kind=leftouter (PricingUnits | extend x_PricingBlockSize = todecimal(x_PricingBlockSize)) on x_PricingUnitDescription\r\n //\r\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, todecimal('')) // Savings plan prices are for the effective price, not the contracted price\r\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\r\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\r\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\r\n | project\r\n BillingAccountId = tolower(case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n BillingAccountId startswith '/', BillingAccountId,\r\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\r\n )),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\r\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\r\n ChargeCategory,\r\n CommitmentDiscountCategory = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Usage',\r\n x_SkuPriceType == 'SavingsPlan', 'Spend',\r\n ''\r\n ),\r\n CommitmentDiscountType = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\r\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\r\n ''\r\n ),\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed',\r\n ''\r\n ),\r\n PricingUnit,\r\n SkuId = coalesce(ProductId, ProductID),\r\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n strlen(x_BillingAccountId) > 32, 'MCA',\r\n strlen(x_BillingAccountId) < 32, 'EA',\r\n 'Unknown'\r\n ),\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\r\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingCurrency = coalesce(Currency, CurrencyCode), // CurrencyCode last as a fallback only\r\n x_PricingSubcategory = case(\r\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_SkuDescription = Product,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\r\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\r\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\r\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\r\n}\r\n\r\n// Prices_final_v1_0 table\r\n.create-merge table Prices_final_v1_0 (\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n ChargeCategory: string,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountType: string,\r\n ContractedUnitPrice: decimal,\r\n ListUnitPrice: decimal,\r\n PricingCategory: string,\r\n PricingUnit: string,\r\n SkuId: string,\r\n SkuPriceId: string,\r\n SkuPriceIdv2: string, // Hubs add-on\r\n x_BaseUnitPrice: decimal, // Azure\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure MCA\r\n x_BillingProfileId: string, // Azure MCA\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_ContractedUnitPriceDiscount: decimal, // Hubs add-on\r\n x_ContractedUnitPriceDiscountPercent: decimal, // Hubs add-on\r\n x_EffectivePeriodEnd: datetime, // Azure\r\n x_EffectivePeriodStart: datetime, // Azure\r\n x_EffectiveUnitPrice: decimal, // Azure\r\n x_EffectiveUnitPriceDiscount: decimal, // Hubs add-on\r\n x_EffectiveUnitPriceDiscountPercent: decimal, // Hubs add-on\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_PricingBlockSize: decimal, // Hubs add-on\r\n x_PricingCurrency: string, // Azure\r\n x_PricingSubcategory: string, // Hubs add-on\r\n x_PricingUnitDescription: string, // Azure\r\n x_SkuDescription: string, // Azure\r\n x_SkuId: string, // Azure\r\n x_SkuIncludedQuantity: decimal, // Azure EA\r\n x_SkuMeterCategory: string, // Azure\r\n x_SkuMeterId: string, // Azure\r\n x_SkuMeterName: string, // Azure\r\n x_SkuMeterSubcategory: string, // Azure\r\n x_SkuMeterType: string, // Azure\r\n x_SkuPriceType: string, // Azure\r\n x_SkuProductId: string, // Azure\r\n x_SkuRegion: string, // Azure\r\n x_SkuServiceFamily: string, // Azure\r\n x_SkuOfferId: string, // Azure EA\r\n x_SkuPartNumber: string, // Azure EA\r\n x_SkuTerm: int, // Azure\r\n x_SkuTier: decimal, // Azure MCA\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_TotalUnitPriceDiscount: decimal, // Hubs add-on\r\n x_TotalUnitPriceDiscountPercent: decimal // Hubs add-on\r\n)\r\n\r\n// Update policy for Prices_raw -> Prices_final_v1_0\r\n.alter table Prices_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Prices_raw\",\r\n \"Query\": \"Prices_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Cost and usage |=================================================================================================\r\n// Supported versions:\r\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All costs transformed to FOCUS 1.0. Use Costs_transform_v1_2() instead.', folder='Costs')\r\nCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n Costs_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n ContractedCost = todecimal(ContractedCost),\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n EffectiveCost = todecimal(EffectiveCost),\r\n ListCost = todecimal(ListCost),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n UsageAmount = todecimal(UsageAmount),\r\n UsageQuantity = todecimal(UsageQuantity),\r\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\r\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\r\n x_Cost = todecimal(x_Cost),\r\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\r\n x_OnDemandCost = todecimal(x_OnDemandCost),\r\n x_OnDemandCostInUsd = todecimal(x_OnDemandCostInUsd),\r\n x_OnDemandUnitPrice = todecimal(x_OnDemandUnitPrice),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\r\n //\r\n // Dedupe rows\r\n | extend x_IngestionTime = ingestion_time()\r\n | extend x_ChargeId = ''\r\n // TODO: Consider adding a unique charge ID per row\r\n // hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // // 1. Resource hierarchy (including resource name), highest to lowest\r\n // BillingAccountId,\r\n // x_InvoiceSectionId,\r\n // x_AccountOwnerId,\r\n // SubAccountId,\r\n // x_ResourceGroupName,\r\n // ResourceName,\r\n // // 2. Resource details\r\n // ResourceId,\r\n // RegionId,\r\n // Tags,\r\n // CommitmentDiscountId,\r\n // x_CostCenter,\r\n // // 4. Meter details\r\n // SkuPriceId,\r\n // x_SkuMeterId,\r\n // x_SkuPartNumber,\r\n // x_SkuOfferId,\r\n // x_SkuDetails,\r\n // // 5. Date\r\n // ChargePeriodStart\r\n // ))\r\n //\r\n // Identify data quality issues\r\n | extend x_SourceChanges = trim_end(',', strcat(\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\r\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\r\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\r\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\r\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\r\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\r\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\r\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\r\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\r\n 'XEffectiveUnitPriceRoundingError,', ''),\r\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\r\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\r\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\r\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\r\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\r\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\r\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\r\n ))\r\n //\r\n // Fix columns needed in other changes\r\n | extend ProviderName = case(\r\n isnotempty(ProviderName), ProviderName,\r\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\r\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\r\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\r\n ''\r\n )\r\n //\r\n // Identify source\r\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\r\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\r\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\r\n ''\r\n ))\r\n // Append version check error code\r\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\r\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\r\n )\r\n //\r\n // Fix quantities\r\n | extend PricingQuantity = case(\r\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\r\n PricingQuantity\r\n )\r\n | extend ConsumedQuantity = case(\r\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\r\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, decimal(1)),\r\n ConsumedQuantity\r\n )\r\n //\r\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\r\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\r\n and (ListUnitPrice == 0 or ContractedUnitPrice == 0)\r\n and x_EffectiveUnitPrice != 0\r\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\r\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\r\n | as allCosts\r\n | where tmp_MissingPrices\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | as costsWithMissingPrices\r\n | join kind=leftouter (\r\n Prices_final_v1_0\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\r\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\r\n ) on tmp_ReservationPriceLookupKey\r\n //\r\n // Select the best price to use for each row\r\n // TODO: Save values before changing -- | extend x_old_ContractedUnitPrice = ContractedUnitPrice, x_old_EffectiveUnitPrice = x_EffectiveUnitPrice, x_old_ListUnitPrice = ListUnitPrice, x_old_ListCost = ListCost, x_old_ContractedCost = ContractedCost\r\n | extend x_EffectiveUnitPrice = case(\r\n // If price is a rounding error away from the billed price, use the billed price\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\r\n // If price is a rounding error away from the contracted price, use the contracted price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ContractedUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\r\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ListUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // Otherwise, assume the contracted price is the same as list price to support aggregations\r\n ContractedUnitPrice\r\n )\r\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\r\n | extend ContractedCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\r\n // ContractedCost is 0 in all other scenarios...\r\n // If 0 and there's a billed cost and prices are the same, use BilledCost\r\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\r\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\r\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume EffectiveCost\r\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ContractedCost\r\n )\r\n | extend ListCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\r\n // ListCost is 0 in all other scenarios...\r\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\r\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume ContractedCost\r\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ListCost\r\n )\r\n // Merge the rest of the unmodified cost records and remove excess columns\r\n | union (allCosts | where not(tmp_MissingPrices))\r\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\r\n //\r\n // BUG: Fix ContractedCost that has bad values\r\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\r\n //\r\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\r\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), todecimal(''))\r\n | extend ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\r\n //\r\n // Convert IDs to lowercase for consistency\r\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\r\n //\r\n // BUG: Remove EffectiveCost for commitment discount purchases\r\n | extend EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), EffectiveCost)\r\n | extend x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), x_EffectiveCostInUsd)\r\n //\r\n // Clean up resource columns\r\n | extend ResourceId = case(\r\n isnotempty(ResourceId), ResourceId,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\r\n ResourceId)\r\n | extend ResourceName = tolower(case(\r\n isnotempty(ResourceName), ResourceName,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\r\n ResourceName))\r\n | extend x_ResourceType = case(\r\n isnotempty(x_ResourceType), x_ResourceType,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\r\n x_ResourceType)\r\n | extend ResourceType = case(\r\n // Use existing resource type display name unless it's an internal resource type ID\r\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\r\n // Use CommitmentDiscountType for commitment discount purchases\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\r\n // Look up display name from internal type\r\n isnotempty(x_ResourceType), coalesce(resource_type(x_ResourceType).SingularDisplayName, ResourceType, x_ResourceType),\r\n ResourceType)\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId = tolower(BillingAccountId),\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEnd),\r\n BillingPeriodStart = startofmonth(BillingPeriodStart),\r\n ChargeCategory = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Credit', 'Credit',\r\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\r\n ChargeCategory\r\n ),\r\n ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass),\r\n ChargeDescription,\r\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\r\n ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency),\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = tolower(CommitmentDiscountId),\r\n CommitmentDiscountName,\r\n CommitmentDiscountStatus = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Used Commitment', 'Used',\r\n ChargeSubcategory == 'Unused Commitment', 'Unused',\r\n CommitmentDiscountStatus\r\n ),\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\r\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\r\n EffectiveCost,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n // Handle FOCUS 1.0-preview PricingCategory values\r\n PricingCategory == 'On-Demand', 'Standard',\r\n PricingCategory == 'Commitment-Based', 'Committed',\r\n PricingCategory\r\n ),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n // Handle missing PublisherName values\r\n PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, ''),\r\n // Handle FOCUS 1.0-preview Region column\r\n RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region)),\r\n RegionName = coalesce(RegionName, Region),\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SkuId,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType, // Azure 1.0-preview(v1)+\r\n Tags = parse_json(Tags),\r\n x_AccountId, // Azure 1.0-preview(v1)+\r\n x_AccountName, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId, // Azure 1.0-preview(v1)+\r\n x_BilledCostInUsd, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement = case(\r\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\r\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\r\n ProviderName\r\n ), // Hubs add-on\r\n x_BillingAccountId, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate, // Azure 1.0-preview(v1)+\r\n x_BillingProfileId, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName, // Azure 1.0-preview(v1)+\r\n x_ChargeId, // Azure 1.0-preview(v1) only\r\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd), // Azure 1.0+\r\n x_CostAllocationRuleName, // Azure 1.0-preview(v1)+\r\n x_CostCategories = parse_json(x_CostCategories), // AWS 1.0 (JSON)\r\n x_CostCenter, // Azure 1.0-preview(v1)+\r\n x_Credits = parse_json(x_Credits), // GCP Jan 2024\r\n x_CostType, // GCP Jan 2024\r\n x_CurrencyConversionRate, // GCP Jun 2024\r\n x_CustomerId, // Azure 1.0-preview(v1)+\r\n x_CustomerName, // Azure 1.0-preview(v1)+\r\n x_Discount = parse_json(x_Discount), // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice, // Azure 1.0-preview(v1)+\r\n x_ExportTime, // GCP Jan 2024\r\n x_IngestionTime, // Hubs add-on\r\n x_InvoiceId = coalesce(InvoiceId, x_InvoiceId), // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId = case( // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId == '-2', '',\r\n x_InvoiceSectionId\r\n ),\r\n x_InvoiceSectionName = case( // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName == 'Unassigned', '',\r\n x_InvoiceSectionName\r\n ),\r\n x_ListCostInUsd, // Azure 1.0-preview(v1)+\r\n x_Location, // GCP Jan 2024\r\n x_Operation, // AWS 1.0\r\n x_PartnerCreditApplied, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency), // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription, // Azure 1.0-preview(v1)+\r\n x_Project, // GCP Jan 2024\r\n x_PublisherCategory, // Azure 1.0-preview(v1)+\r\n x_PublisherId, // Azure 1.0-preview(v1)+\r\n x_ResellerId, // Azure 1.0-preview(v1)+\r\n x_ResellerName, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName = tolower(x_ResourceGroupName), // Azure 1.0-preview(v1)+\r\n x_ResourceType, // Azure 1.0-preview(v1)+\r\n x_ServiceCode, // AWS 1.0\r\n x_ServiceId, // GCP Jan 2024\r\n x_ServicePeriodEnd, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart, // Azure 1.0-preview(v1)+\r\n x_SkuDescription, // Azure 1.0-preview(v1)+\r\n x_SkuDetails = parse_json(x_SkuDetails), // Azure 1.0-preview(v1)+\r\n x_SkuIsCreditEligible, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName = coalesce(SkuMeter, x_SkuMeterName), // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber, // Azure 1.0-preview(v1)+\r\n x_SkuRegion, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily, // Azure 1.0-preview(v1)+\r\n x_SkuTerm, // Azure 1.0-preview(v1)+\r\n x_SkuTier, // Azure 1.0-preview(v1)+\r\n x_SourceChanges, // Hubs add-on\r\n x_SourceName, // Hubs add-on\r\n x_SourceProvider, // Hubs add-on\r\n x_SourceType, // Hubs add-on\r\n x_SourceVersion, // Hubs add-on\r\n x_UsageType // AWS 1.0\r\n}\r\n\r\n// Costs_final_v1_0 table\r\n.create-merge table Costs_final_v1_0 (\r\n AvailabilityZone: string,\r\n BilledCost: decimal,\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingAccountType: string, // Azure 1.0-preview(v1)+\r\n BillingCurrency: string,\r\n BillingPeriodEnd: datetime,\r\n BillingPeriodStart: datetime,\r\n ChargeCategory: string,\r\n ChargeClass: string,\r\n ChargeDescription: string,\r\n ChargeFrequency: string,\r\n ChargePeriodEnd: datetime,\r\n ChargePeriodStart: datetime,\r\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview only\r\n CommitmentDiscountId: string,\r\n CommitmentDiscountName: string,\r\n CommitmentDiscountStatus: string,\r\n CommitmentDiscountType: string,\r\n ConsumedQuantity: decimal,\r\n ConsumedUnit: string,\r\n ContractedCost: decimal,\r\n ContractedUnitPrice: decimal,\r\n EffectiveCost: decimal,\r\n InvoiceIssuerName: string,\r\n ListCost: decimal,\r\n ListUnitPrice: decimal,\r\n PricingCategory: string,\r\n PricingQuantity: decimal,\r\n PricingUnit: string,\r\n ProviderName: string,\r\n PublisherName: string,\r\n RegionId: string,\r\n RegionName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n ServiceCategory: string,\r\n ServiceName: string,\r\n SkuId: string,\r\n SkuPriceId: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n SubAccountType: string,\r\n Tags: dynamic,\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_BilledCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: decimal, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: decimal, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_ContractedCostInUsd: decimal, // Azure 1.0+\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_Credits: dynamic, // GCP Jan 2024\r\n x_CostType: string, // GCP Jan 2024\r\n x_CurrencyConversionRate: decimal, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: dynamic, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: decimal, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_Operation: string, // AWS 1.0\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: decimal, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency: string, // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceChanges: string, // Hubs add-on\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Update policy for Costs_raw -> Costs_final_v1_0 table\r\n.alter table Costs_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Costs_raw\",\r\n \"Query\": \"Costs_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Actual costs |===================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use ActualCosts_transform_v1_2() instead.', folder='Costs')\r\nActualCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n // TODO: Transform actual costs to FOCUS 1.0 format\r\n ActualCosts_raw\r\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodStart = Date,\r\n ChargePeriodEnd = Date + 1d,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = '',\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory = '',\r\n SkuId = '',\r\n SkuMeter = '',\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = SubscriptionName,\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentName = '',\r\n x_ComponentType = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel = '',\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for ActualCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"ActualCosts_raw\",\r\n \"Query\": \"ActualCosts_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Amortized costs |================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use AmortizedCosts_transform_v1_2() instead.', folder='Costs')\r\nAmortizedCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n // TODO: Transform actual costs to FOCUS 1.0 format\r\n AmortizedCosts_raw\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodStart = Date,\r\n ChargePeriodEnd = Date + 1d,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = '',\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory = '',\r\n SkuId = '',\r\n SkuMeter = '',\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = SubscriptionName,\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentName = '',\r\n x_ComponentType = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel = '',\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for AmortizedCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"AmortizedCosts_raw\",\r\n \"Query\": \"AmortizedCosts_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All commitment discount usage transformed to FOCUS 1.0. This includes reservationdeatils_raw. Use CommitmentDiscountUsage_transform_v1_2() instead.', folder='Commitment discounts')\r\nCommitmentDiscountUsage_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n CommitmentDiscountUsage_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\r\n ReservedHours = todecimal(ReservedHours),\r\n TotalReservedQuantity = todecimal(TotalReservedQuantity),\r\n UsedHours = todecimal(UsedHours)\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Handle resource columns\r\n | extend ResourceId = tolower(InstanceId)\r\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\r\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\r\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\r\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\r\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\r\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, x_ServiceModel) on x_ResourceType\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n ChargePeriodEnd = UsageDate + 1d,\r\n ChargePeriodStart = UsageDate,\r\n CommitmentDiscountCategory = 'Usage',\r\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\r\n CommitmentDiscountType = 'Reservation',\r\n ConsumedQuantity = UsedHours,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\r\n x_CommitmentDiscountCommittedAmount = ReservedHours,\r\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\r\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\r\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\r\n x_CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\r\n x_IngestionTime = ingestion_time(),\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n // x_RowId = hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // CommitmentDiscountId,\r\n // ResourceId,\r\n // ChargePeriodStart\r\n // )),\r\n x_ServiceModel,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\r\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\r\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\r\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\r\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\r\n}\r\n\r\n// CommitmentDiscountUsage_final_v1_0 table\r\n.create-merge table CommitmentDiscountUsage_final_v1_0 (\r\n ChargePeriodEnd: datetime, // Hubs add-on\r\n ChargePeriodStart: datetime, // MS 2023-03-01\r\n CommitmentDiscountCategory: string, // Hubs add-on\r\n CommitmentDiscountId: string, // MS 2023-03-01\r\n CommitmentDiscountType: string, // Hubs add-on\r\n ConsumedQuantity: decimal, // MS 2023-03-01\r\n ProviderName: string, // Hubs add-on\r\n ResourceId: string, // MS 2023-03-01\r\n ResourceName: string, // Hubs add-on\r\n ResourceType: string, // Hubs add-on\r\n ServiceCategory: string, // Hubs add-on\r\n ServiceName: string, // Hubs add-on\r\n SubAccountId: string, // Hubs add-on\r\n x_CommitmentDiscountCommittedCount: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountCommittedAmount: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedRatio: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountQuantity: decimal, // MS 2023-03-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_ResourceGroupName: string, // Hubs add-on\r\n x_ResourceType: string, // Hubs add-on\r\n x_ServiceModel: string, // Hubs add-on\r\n x_SkuOrderId: string, // MS 2023-03-01\r\n x_SkuSize: string, // MS 2023-03-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string // Hubs add-on\r\n)\r\n\r\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_0 table\r\n.alter table CommitmentDiscountUsage_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"CommitmentDiscountUsage_raw\",\r\n \"Query\": \"CommitmentDiscountUsage_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All recommendations transformed to FOCUS 1.0. Use Recommendations_transform_v1_2() instead.', folder='Recommendations')\r\nRecommendations_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Recommendations_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n CostWithNoReservedInstances = todecimal(CostWithNoReservedInstances),\r\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\r\n NetSavings = todecimal(NetSavings),\r\n RecommendedQuantity = todecimal(RecommendedQuantity),\r\n RecommendedQuantityNormalized = todecimal(RecommendedQuantityNormalized),\r\n TotalCostWithReservedInstances = todecimal(TotalCostWithReservedInstances)\r\n //\r\n | extend x_IngestionTime = ingestion_time()\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Convert JSON cost columns to decimal\r\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\r\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\r\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\r\n //\r\n // Build recommendation details\r\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\r\n | extend x_RecommendationDetails = case(\r\n x_SourceType == 'ReservationRecommendations', bag_pack(\r\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\r\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\r\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\r\n 'CommitmentDiscountResourceType', ResourceType,\r\n 'CommitmentDiscountScope', Scope,\r\n 'LookbackPeriodDuration', case(\r\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\r\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\r\n ''\r\n ),\r\n 'LookbackPeriodStart', FirstUsageDate,\r\n 'RecommendedQuantity', RecommendedQuantity,\r\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\r\n 'RegionId', Location,\r\n 'RegionName', RegionName,\r\n 'SkuMeterId', MeterId,\r\n 'SkuPriceDetails', SkuProperties,\r\n 'SkuSize', coalesce(SKU, SkuName),\r\n 'SkuTerm', isoMonths(Term)\r\n ),\r\n dynamic({})\r\n )\r\n //\r\n // Sort columns and apply final transforms\r\n | extend x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d)\r\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\r\n | project\r\n ProviderName,\r\n SubAccountId = iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), ''),\r\n x_IngestionTime,\r\n x_EffectiveCostAfter = TotalCostWithReservedInstances,\r\n x_EffectiveCostBefore = CostWithNoReservedInstances,\r\n x_EffectiveCostSavings = NetSavings,\r\n x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d),\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n// Recommendations_final_v1_0 table\r\n.create-merge table Recommendations_final_v1_0 (\r\n ProviderName: string,\r\n SubAccountId: string,\r\n x_IngestionTime: datetime,\r\n x_EffectiveCostAfter: decimal,\r\n x_EffectiveCostBefore: decimal,\r\n x_EffectiveCostSavings: decimal,\r\n x_RecommendationDate: datetime,\r\n x_RecommendationDetails: dynamic,\r\n x_SourceName: string,\r\n x_SourceProvider: string,\r\n x_SourceType: string,\r\n x_SourceVersion: string\r\n)\r\n\r\n// Update policy for Recommendations_raw -> Recommendations_final_v1_0 table\r\n.alter table Recommendations_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Recommendations_raw\",\r\n \"Query\": \"Recommendations_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All transactions transformed to FOCUS 1.0. Use Transactions_transform_v1_2() instead.', folder='Transactions')\r\nTransactions_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Transactions_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n Amount = todecimal(Amount),\r\n MonetaryCommitment = todecimal(MonetaryCommitment),\r\n Overage = todecimal(Overage),\r\n Quantity = todecimal(Quantity)\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Handle BillingPeriodStart/End\r\n | extend BillingMonth = tostring(BillingMonth)\r\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\r\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n BilledCost = Amount,\r\n BillingAccountId = case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\r\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\r\n ''\r\n ),\r\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\r\n BillingCurrency = Currency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory = case(\r\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = case(\r\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\r\n EventType == 'Refund', 'Correction',\r\n ''\r\n ),\r\n ChargeDescription = Description,\r\n ChargeFrequency = case(\r\n BillingFrequency == 'OneTime', 'One-Time',\r\n BillingFrequency == 'Recurring', 'Recurring',\r\n BillingFrequency\r\n ),\r\n ChargePeriodStart = EventDate,\r\n PricingQuantity = Quantity,\r\n PricingUnit = 'Reservations',\r\n ProviderName,\r\n RegionId = Region,\r\n RegionName = Region,\r\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\r\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerEmail,\r\n x_CostCenter = CostCenter,\r\n x_InvoiceId = InvoiceId,\r\n x_InvoiceNumber = Invoice,\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\r\n x_IngestionTime = ingestion_time(),\r\n x_MonetaryCommitment = MonetaryCommitment,\r\n x_Overage = Overage,\r\n x_PurchasingBillingAccountId = PurchasingEnrollment,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuOrderName = ReservationOrderName,\r\n x_SkuSize = ArmSkuName,\r\n x_SkuTerm = isoMonths(Term),\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId = PurchasingSubscriptionGuid,\r\n x_TransactionType = EventType\r\n}\r\n\r\n// Transactions_final_v1_0 table\r\n.create-merge table Transactions_final_v1_0 (\r\n BilledCost: decimal, // MS CM EA+MCA 2023-05-01\r\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\r\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\r\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n ChargeCategory: string, // Hubs add-on\r\n ChargeClass: string, // Hubs add-on\r\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\r\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\r\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n PricingQuantity: decimal, // MS CM EA+MCA 2023-05-01\r\n PricingUnit: string, // Hubs add-on\r\n ProviderName: string, // Hubs add-on\r\n RegionId: string, // MS CM EA+MCA 2023-05-01\r\n RegionName: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\r\n x_AccountName: string, // MS CM EA 2023-05-01\r\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\r\n x_CostCenter: string, // MS CM EA 2023-05-01\r\n x_InvoiceId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_MonetaryCommitment: decimal, // MS CM EA 2023-05-01\r\n x_Overage: decimal, // MS CM EA 2023-05-01\r\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\r\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\r\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\r\n)\r\n\r\n// Update policy for Transactions_raw -> Transactions_final_v1_0 table\r\n.alter table Transactions_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Transactions_raw\",\r\n \"Query\": \"Transactions_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n", - "dataExplorerPrivateDnsZoneName": "[replace(format('privatelink.{0}.{1}', parameters('location'), replace(environment().suffixes.storage, 'core', 'kusto')), '..', '.')]", - "ingestionCapacity": { - "Dev(No SLA)_Standard_E2a_v4": 1, - "Dev(No SLA)_Standard_D11_v2": 1, - "Standard_D11_v2": 2, - "Standard_D12_v2": 4, - "Standard_D13_v2": 8, - "Standard_D14_v2": 16, - "Standard_D16d_v5": 16, - "Standard_D32d_v4": 32, - "Standard_D32d_v5": 32, - "Standard_DS13_v2+1TB_PS": 8, - "Standard_DS13_v2+2TB_PS": 8, - "Standard_DS14_v2+3TB_PS": 16, - "Standard_DS14_v2+4TB_PS": 16, - "Standard_E2a_v4": 2, - "Standard_E2ads_v5": 2, - "Standard_E2d_v4": 2, - "Standard_E2d_v5": 2, - "Standard_E4a_v4": 4, - "Standard_E4ads_v5": 4, - "Standard_E4d_v4": 4, - "Standard_E4d_v5": 4, - "Standard_E8a_v4": 8, - "Standard_E8ads_v5": 8, - "Standard_E8as_v4+1TB_PS": 8, - "Standard_E8as_v4+2TB_PS": 8, - "Standard_E8as_v5+1TB_PS": 8, - "Standard_E8as_v5+2TB_PS": 8, - "Standard_E8d_v4": 8, - "Standard_E8d_v5": 8, - "Standard_E8s_v4+1TB_PS": 8, - "Standard_E8s_v4+2TB_PS": 8, - "Standard_E8s_v5+1TB_PS": 8, - "Standard_E8s_v5+2TB_PS": 8, - "Standard_E16a_v4": 16, - "Standard_E16ads_v5": 16, - "Standard_E16as_v4+3TB_PS": 16, - "Standard_E16as_v4+4TB_PS": 16, - "Standard_E16as_v5+3TB_PS": 16, - "Standard_E16as_v5+4TB_PS": 16, - "Standard_E16d_v4": 16, - "Standard_E16d_v5": 16, - "Standard_E16s_v4+3TB_PS": 16, - "Standard_E16s_v4+4TB_PS": 16, - "Standard_E16s_v5+3TB_PS": 16, - "Standard_E16s_v5+4TB_PS": 16, - "Standard_E64i_v3": 64, - "Standard_E80ids_v4": 80, - "Standard_EC8ads_v5": 8, - "Standard_EC8as_v5+1TB_PS": 8, - "Standard_EC8as_v5+2TB_PS": 8, - "Standard_EC16ads_v5": 16, - "Standard_EC16as_v5+3TB_PS": 16, - "Standard_EC16as_v5+4TB_PS": 16, - "Standard_L4s": 4, - "Standard_L8as_v3": 8, - "Standard_L8s": 8, - "Standard_L8s_v2": 8, - "Standard_L8s_v3": 8, - "Standard_L16as_v3": 16, - "Standard_L16s": 16, - "Standard_L16s_v2": 16, - "Standard_L16s_v3": 16, - "Standard_L32as_v3": 32, - "Standard_L32s_v3": 32 - } + "$fxv#0": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\"}\n },\n {\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\"}\n },\n {\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerId\"}\n },\n {\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionId\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionName\"}\n },\n {\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"Date\"}\n },\n {\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\"}\n },\n {\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\"}\n },\n {\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\"}\n },\n {\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\"}\n },\n {\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\"}\n },\n {\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\"}\n },\n {\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\"}\n },\n {\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectivePrice\"}\n },\n {\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Cost\"}\n },\n {\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\"}\n },\n {\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\"}\n },\n {\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceLocation\"}\n },\n {\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\"}\n },\n {\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedService\"}\n },\n {\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\"}\n },\n {\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo1\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo2\"}\n },\n {\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AdditionalInfo\"}\n },\n {\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSection\"}\n },\n {\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\"}\n },\n {\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\"}\n },\n {\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceGroup\"}\n },\n {\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\"}\n },\n {\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationName\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderId\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderName\"}\n },\n {\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferId\"}\n },\n {\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\n },\n {\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\"}\n },\n {\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\"}\n },\n {\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PlanName\"}\n },\n {\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeType\"}\n },\n {\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Frequency\"}\n },\n {\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherType\"}\n }\n ]\n }\n}\n", + "$fxv#1": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\"}\n },\n {\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\"}\n },\n {\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerId\"}\n },\n {\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionId\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionName\"}\n },\n {\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"Date\"}\n },\n {\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\"}\n },\n {\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\"}\n },\n {\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\"}\n },\n {\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\"}\n },\n {\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\"}\n },\n {\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\"}\n },\n {\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\"}\n },\n {\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectivePrice\"}\n },\n {\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Cost\"}\n },\n {\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\"}\n },\n {\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\"}\n },\n {\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceLocation\"}\n },\n {\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\"}\n },\n {\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedService\"}\n },\n {\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\"}\n },\n {\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo1\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo2\"}\n },\n {\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AdditionalInfo\"}\n },\n {\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSection\"}\n },\n {\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\"}\n },\n {\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\"}\n },\n {\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceGroup\"}\n },\n {\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\"}\n },\n {\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationName\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderId\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderName\"}\n },\n {\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferId\"}\n },\n {\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\n },\n {\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\"}\n },\n {\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\"}\n },\n {\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PlanName\"}\n },\n {\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeType\"}\n },\n {\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Frequency\"}\n },\n {\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherType\"}\n }\n ]\n }\n}\n", + "$fxv#10": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"SKU\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SKU\" }\n },\n {\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Location\" }\n },\n {\n \"source\": { \"name\": \"CostWithNoReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostWithNoReservedInstances\" }\n },\n {\n \"source\": { \"name\": \"FirstUsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"FirstUsageDate\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\n \"sink\": { \"name\": \"LookBackPeriod\" }\n },\n {\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"NetSavings\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"NetSavings\" }\n },\n {\n \"source\": { \"name\": \"NormalizedSize\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NormalizedSize\" }\n },\n {\n \"source\": { \"name\": \"RecommendedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantity\" }\n },\n {\n \"source\": { \"name\": \"RecommendedQuantityNormalized\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"Scope\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Scope\" }\n },\n {\n \"source\": { \"name\": \"SkuProperties\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuProperties\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"TotalCostWithReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TotalCostWithReservedInstances\" }\n }\n ]\n }\n}\n", + "$fxv#11": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"Cost With No ReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostWithNoReservedInstancesJson\" }\n },\n {\n \"source\": { \"name\": \"First UsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"FirstUsageDate\" }\n },\n {\n \"source\": { \"name\": \"Instance Flexibility Ratio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"Instance Flexibility Group\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Location\" }\n },\n {\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\n \"sink\": { \"name\": \"LookBackPeriod\" }\n },\n {\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"Net Savings\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NetSavingsJson\" }\n },\n {\n \"source\": { \"name\": \"Normalized Size\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NormalizedSize\" }\n },\n {\n \"source\": { \"name\": \"Recommended Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantity\" }\n },\n {\n \"source\": { \"name\": \"Recommended Quantity Normalized\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"scope\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Scope\" }\n },\n {\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuName\" }\n },\n {\n \"source\": { \"name\": \"Sku Properties\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuProperties\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"Total Cost With ReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TotalCostWithReservedInstancesJson\" }\n }\n ]\n }\n}\n", + "$fxv#12": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\" }\n },\n {\n \"source\": { \"name\": \"AccountOwnerEmail\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerEmail\" }\n },\n {\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Amount\" }\n },\n {\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ArmSkuName\" }\n },\n {\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingFrequency\" }\n },\n {\n \"source\": { \"name\": \"BillingMonth\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingMonth\" }\n },\n {\n \"source\": { \"name\": \"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"CurrentEnrollmentId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CurrentEnrollmentId\" }\n },\n {\n \"source\": { \"name\": \"DepartmentName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"DepartmentName\" }\n },\n {\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Description\" }\n },\n {\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EventDate\" }\n },\n {\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EventType\" }\n },\n {\n \"source\": { \"name\": \"MonetaryCommitment\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MonetaryCommitment\" }\n },\n {\n \"source\": { \"name\": \"Overage\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Overage\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\n },\n {\n \"source\": { \"name\": \"PurchasingEnrollment\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingEnrollment\" }\n },\n {\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderName\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n }\n ]\n }\n}\n", + "$fxv#13": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Amount\" }\n },\n {\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ArmSkuName\" }\n },\n {\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingFrequency\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Description\" }\n },\n {\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EventDate\" }\n },\n {\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EventType\" }\n },\n {\n \"source\": { \"name\": \"Invoice\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Invoice\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\n },\n {\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderName\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n }\n ]\n }\n}\n", + "$fxv#2": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationId\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceSubcategory\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuMeter\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceDetails\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AmortizationClass\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ServiceModel\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPlanName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", + "$fxv#3": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationId\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceSubcategory\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuMeter\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceDetails\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AmortizationClass\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ServiceModel\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPlanName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", + "$fxv#4": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", + "$fxv#5": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", + "$fxv#6": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\" }\n },\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeSubcategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"UsageQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UsageQuantity\" }\n },\n {\n \"source\": { \"name\": \"UsageUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UsageUnit\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ChargeId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ChargeId\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandCost\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", + "$fxv#7": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"EnrollmentNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EnrollmentNumber\" }\n },\n {\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterID\" }\n },\n {\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\" }\n },\n {\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterType\" }\n },\n {\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\" }\n },\n {\n \"source\": { \"name\": \"SkuID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuID\" }\n },\n {\n \"source\": { \"name\": \"ProductID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductID\" }\n },\n {\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\" }\n },\n {\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\" }\n },\n {\n \"source\": { \"name\": \"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\" }\n },\n {\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveStartDate\" }\n },\n {\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveEndDate\" }\n },\n {\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\" }\n },\n {\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BasePrice\" }\n },\n {\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MarketPrice\" }\n },\n {\n \"source\": { \"name\": \"CurrencyCode\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CurrencyCode\" }\n },\n {\n \"source\": { \"name\": \"IncludedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"IncludedQuantity\" }\n },\n {\n \"source\": { \"name\": \"OfferID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferID\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PriceType\" }\n }\n ]\n }\n}\n", + "$fxv#8": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\" }\n },\n {\n \"source\": { \"name\": \"ProductId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductId\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\" }\n },\n {\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\" }\n },\n {\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterType\" }\n },\n {\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\" }\n },\n {\n \"source\": { \"name\": \"TierMinimumUnits\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TierMinimumUnits\" }\n },\n {\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveStartDate\" }\n },\n {\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveEndDate\" }\n },\n {\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\" }\n },\n {\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BasePrice\" }\n },\n {\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MarketPrice\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PriceType\" }\n }\n ]\n }\n}\n", + "$fxv#9": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"InstanceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceId\" }\n },\n {\n \"source\": { \"name\": \"Kind\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Kind\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\" }\n },\n {\n \"source\": { \"name\": \"ReservedHours\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ReservedHours\" }\n },\n {\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuName\" }\n },\n {\n \"source\": { \"name\": \"TotalReservedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"TotalReservedQuantity\" }\n },\n {\n \"source\": { \"name\": \"UsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"UsageDate\" }\n },\n {\n \"source\": { \"name\": \"UsedHours\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UsedHours\" }\n }\n ]\n }\n}\n", + "CONFIG": "config", + "INGESTION": "ingestion", + "MSEXPORTS": "msexports", + "ingestionIdFileNameSeparator": "__", + "finOpsToolkitVersion": "12.0" }, - "resources": [ - { - "type": "Microsoft.Kusto/clusters/principalAssignments", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('clusterName'), 'adf-mi-cluster-admin')]", - "properties": { - "principalType": "App", - "principalId": "[reference(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), '2018-06-01', 'full').identity.principalId]", - "tenantId": "[reference(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), '2018-06-01', 'full').identity.tenantId]", - "role": "AllDatabasesAdmin" - }, + "resources": { + "dataFactory::linkedService_storageAccount": { + "existing": true, + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + "appRegistration" ] }, - { - "type": "Microsoft.Kusto/clusters/databases", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('clusterName'), 'Ingestion')]", - "location": "[parameters('location')]", - "kind": "ReadWrite", + "dataFactory::dataset_config": { + "existing": true, + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]", "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + "appRegistration" ] }, - { - "type": "Microsoft.Kusto/clusters/databases", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('clusterName'), 'Hub')]", - "location": "[parameters('location')]", - "kind": "ReadWrite", + "dataFactory::dataset_ingestion": { + "existing": true, + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('INGESTION'))]", "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + "appRegistration" ] }, - { - "type": "Microsoft.Kusto/clusters", - "apiVersion": "2023-08-15", - "name": "[parameters('clusterName')]", - "location": "[parameters('location')]", - "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Kusto/clusters'), createObject()))]", - "sku": { - "name": "[parameters('clusterSku')]", - "tier": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 'Basic', 'Standard')]", - "capacity": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 1, if(equals(parameters('clusterCapacity'), 1), 2, parameters('clusterCapacity')))]" - }, - "identity": { - "type": "SystemAssigned" + "dataFactory::dataset_ingestion_files": { + "existing": true, + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', variables('INGESTION')))]", + "dependsOn": [ + "appRegistration" + ] + }, + "dataFactory::dataset_manifest": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'manifest')]", + "properties": { + "parameters": { + "fileName": { + "type": "String", + "defaultValue": "manifest.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[variables('MSEXPORTS')]" + } + }, + "type": "Json", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().fileName}", + "type": "Expression" + }, + "folderPath": { + "value": "@{dataset().folderPath}", + "type": "Expression" + } + } + }, + "linkedServiceName": { + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + } }, + "dependsOn": [ + "appRegistration" + ] + }, + "dataFactory::dataset_msexports": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, replace(format('{0}', variables('MSEXPORTS')), '-', '_'))]", "properties": { - "enableStreamingIngest": true, - "enableAutoStop": false, - "publicNetworkAccess": "[if(parameters('enablePublicAccess'), 'Enabled', 'Disabled')]" - } + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" + }, + "fileSystem": "[reference('exportContainer').outputs.containerName.value]" + }, + "columnDelimiter": ",", + "escapeChar": "\"", + "quoteChar": "\"", + "firstRowAsHeader": true + }, + "linkedServiceName": { + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "appRegistration", + "exportContainer" + ] }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", - "name": "[guid(parameters('clusterName'), subscription().id, 'Storage Blob Data Contributor')]", + "dataFactory::dataset_msexports_gzip": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_gzip', variables('MSEXPORTS')))]", "properties": { - "description": "Give \"Storage Blob Data Contributor\" to the cluster", - "principalId": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15', 'full').identity.principalId]", - "principalType": "ServicePrincipal", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" + }, + "fileSystem": "[variables('MSEXPORTS')]" + }, + "columnDelimiter": ",", + "escapeChar": "\"", + "quoteChar": "\"", + "firstRowAsHeader": true, + "compressionCodec": "Gzip" + }, + "linkedServiceName": { + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + } }, "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + "appRegistration" ] }, - { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[variables('dataExplorerPrivateDnsZoneName')]", - "location": "global", - "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateDnsZones'), createObject()))]", - "properties": {} - }, - { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('dataExplorerPrivateDnsZoneName'), format('{0}-link', replace(variables('dataExplorerPrivateDnsZoneName'), '.', '-')))]", - "location": "global", - "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks'), createObject()))]", + "dataFactory::dataset_msexports_parquet": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_parquet', variables('MSEXPORTS')))]", "properties": { - "virtualNetwork": { - "id": "[parameters('virtualNetworkId')]" + "parameters": { + "blobPath": { + "type": "String" + } }, - "registrationEnabled": false + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" + }, + "fileSystem": "[variables('MSEXPORTS')]" + } + }, + "linkedServiceName": { + "referenceName": "[parameters('app').storage]", + "type": "LinkedServiceReference" + } }, "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" + "appRegistration" ] }, - { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('clusterName'))]", - "location": "[parameters('location')]", - "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateEndpoints'), createObject()))]", + "dataFactory::pipeline_ExecuteExportsETL": { + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ExecuteETL', variables('MSEXPORTS')))]", "properties": { - "subnet": { - "id": "[parameters('privateEndpointSubnetId')]" - }, - "privateLinkServiceConnections": [ + "activities": [ + { + "name": "Wait", + "description": "Files may not be available immediately after being created.", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 60 + } + }, + { + "name": "Read Manifest", + "description": "Load the export manifest to determine the scope, dataset, and date range.", + "type": "Lookup", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Completed" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@pipeline().parameters.fileName", + "type": "Expression" + }, + "folderPath": { + "value": "@pipeline().parameters.folderPath", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Has No Rows", + "description": "Check the row count ", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "hasNoRows", + "value": { + "value": "@or(equals(activity('Read Manifest').output.firstRow.blobCount, null), equals(activity('Read Manifest').output.firstRow.blobCount, 0))", + "type": "Expression" + } + } + }, + { + "name": "Set Export Dataset Type", + "description": "Save the dataset type from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "exportDatasetType", + "value": { + "value": "@activity('Read Manifest').output.firstRow.exportConfig.type", + "type": "Expression" + } + } + }, + { + "name": "Set MCA Column", + "description": "Determines if the dataset schema has channel-specific columns and saves the column name that only exists in MCA to determine if it is an MCA dataset.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "mcaColumnToCheck", + "value": { + "value": "@if(contains(createArray('pricesheet', 'reservationtransactions'), toLower(variables('exportDatasetType'))), 'BillingProfileId', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Net Savings', null))", + "type": "Expression" + } + } + }, + { + "name": "Set Export Dataset Version", + "description": "Save the dataset version from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "exportDatasetVersion", + "value": { + "value": "@activity('Read Manifest').output.firstRow.exportConfig.dataVersion", + "type": "Expression" + } + } + }, + { + "name": "Detect Channel", + "description": "Determines what channel this export is from. Switch statement handles the different file types if the mcaColumnToCheck variable is set.", + "type": "Switch", + "dependsOn": [ + { + "activity": "Set Has No Rows", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set MCA Column", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Export Dataset Version", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "on": { + "value": "@if(or(empty(variables('mcaColumnToCheck')), variables('hasNoRows')), 'ignore', last(array(split(activity('Read Manifest').output.firstRow.blobs[0].blobName, '.'))))", + "type": "Expression" + }, + "cases": [ + { + "value": "csv", + "activities": [ + { + "name": "Check for MCA Column in CSV", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "dataset": { + "referenceName": "[replace(format('{0}', variables('MSEXPORTS')), '-', '_')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel in CSV", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in CSV", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in CSV').output, 'firstRow'), contains(activity('Check for MCA Column in CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + }, + { + "value": "gz", + "activities": [ + { + "name": "Check for MCA Column in Gzip CSV", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "dataset": { + "referenceName": "[format('{0}_gzip', variables('MSEXPORTS'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel in Gzip CSV", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in Gzip CSV", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Gzip CSV').output, 'firstRow'), contains(activity('Check for MCA Column in Gzip CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + }, + { + "value": "parquet", + "activities": [ + { + "name": "Check for MCA Column in Parquet", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "ParquetSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + }, + "dataset": { + "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel for Parquet", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in Parquet", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Parquet').output, 'firstRow'), contains(activity('Check for MCA Column in Parquet').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + } + ], + "defaultActivities": [ + { + "name": "Set Schema File", + "type": "SetVariable", + "dependsOn": [], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), '.json'))", + "type": "Expression" + } + } + } + ] + } + }, + { + "name": "Set Scope", + "description": "Save the scope from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "scope", + "value": { + "value": "@split(toLower(activity('Read Manifest').output.firstRow.exportConfig.resourceId), '/providers/microsoft.costmanagement/exports/')[0]", + "type": "Expression" + } + } + }, + { + "name": "Set Date", + "description": "Save the exported month from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "date", + "value": { + "value": "@replace(substring(activity('Read Manifest').output.firstRow.runInfo.startDate, 0, 7), '-', '')", + "type": "Expression" + } + } + }, { - "name": "dataExplorerLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "groupIds": [ - "cluster" + "name": "Failed to Read Manifest", + "type": "Fail", + "dependsOn": [ + { + "activity": "Set Date", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Scope", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Export Dataset Version", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Detect Channel", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Failed to read the manifest file for this export run. Manifest path: ', pipeline().parameters.folderPath)", + "type": "Expression" + }, + "errorCode": "ManifestReadFailed" + } + }, + { + "name": "Check Schema", + "description": "Verify that the schema file exists in storage.", + "type": "GetMetadata", + "dependsOn": [ + { + "activity": "Set Scope", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Date", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Detect Channel", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[variables('CONFIG')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('schemaFile')", + "type": "Expression" + }, + "folderPath": "[format('{0}/schemas', reference('schemaFiles').outputs.containerName.value)]" + } + }, + "fieldList": [ + "exists" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + } + }, + { + "name": "Schema Not Found", + "type": "Fail", + "dependsOn": [ + { + "activity": "Check Schema", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('The ', variables('schemaFile'), ' schema mapping file was not found. Please confirm version ', variables('exportDatasetVersion'), ' of the ', variables('exportDatasetType'), ' dataset is supported by this version of FinOps hubs. You may need to upgrade to a newer release. To add support for another dataset, you can create a custom mapping file.')", + "type": "Expression" + }, + "errorCode": "SchemaNotFound" + } + }, + { + "name": "Set Hub Dataset", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "hubDataset", + "value": { + "value": "@if(equals(toLower(variables('exportDatasetType')), 'focuscost'), 'Costs', if(equals(toLower(variables('exportDatasetType')), 'pricesheet'), 'Prices', if(equals(toLower(variables('exportDatasetType')), 'reservationdetails'), 'CommitmentDiscountUsage', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Recommendations', if(equals(toLower(variables('exportDatasetType')), 'reservationtransactions'), 'Transactions', if(equals(toLower(variables('exportDatasetType')), 'actualcost'), 'ActualCosts', if(equals(toLower(variables('exportDatasetType')), 'amortizedcost'), 'AmortizedCosts', toLower(variables('exportDatasetType')))))))))", + "type": "Expression" + } + } + }, + { + "name": "Set Destination Folder", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check Schema", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Hub Dataset", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "destinationFolder", + "value": { + "value": "@replace(concat(variables('hubDataset'),'/',substring(variables('date'), 0, 4),'/',substring(variables('date'), 4, 2),'/',toLower(variables('scope')), if(equals(variables('hubDataset'), 'Recommendations'), activity('Read Manifest').output.firstRow.exportConfig.exportName, '')),'//','/')", + "type": "Expression" + } + } + }, + { + "name": "For Each Blob", + "description": "Loop thru each exported file listed in the manifest.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Set Destination Folder", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(variables('hasNoRows'), json('[]'), activity('Read Manifest').output.firstRow.blobs)", + "type": "Expression" + }, + "batchCount": "[if(parameters('app').hub.options.privateRouting, 4, 30)]", + "isSequential": false, + "activities": [ + { + "name": "Execute", + "description": "Run the ingestion ETL pipeline.", + "type": "ExecutePipeline", + "dependsOn": [], + "policy": { + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_ETL_{1}', variables('MSEXPORTS'), variables('INGESTION'))]", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "blobPath": { + "value": "@item().blobName", + "type": "Expression" + }, + "destinationFolder": { + "value": "@variables('destinationFolder')", + "type": "Expression" + }, + "destinationFile": { + "value": "@last(array(split(replace(replace(item().blobName, '.gz', ''), '.csv', '.parquet'), '/')))", + "type": "Expression" + }, + "ingestionId": { + "value": "@activity('Read Manifest').output.firstRow.runInfo.runId", + "type": "Expression" + }, + "schemaFile": { + "value": "@variables('schemaFile')", + "type": "Expression" + }, + "exportDatasetType": { + "value": "@variables('exportDatasetType')", + "type": "Expression" + }, + "exportDatasetVersion": { + "value": "@variables('exportDatasetVersion')", + "type": "Expression" + } + } + } + } ] } - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" - ] - }, - { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('clusterName')), 'dataExplorer-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "privatelink-westus-kusto-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" - } - }, - { - "name": "privatelink-blob-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } }, { - "name": "privatelink-table-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.table.{0}', environment().suffixes.storage))]" - } + "name": "Copy Manifest", + "description": "Copy the manifest to the ingestion container to trigger ADX ingestion", + "type": "Copy", + "dependsOn": [ + { + "activity": "For Each Blob", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "sink": { + "type": "JsonSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "JsonWriteSettings" + } + }, + "enableStaging": false + }, + "inputs": [ + { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": "manifest.json", + "folderPath": { + "value": "@pipeline().parameters.folderPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": "manifest.json", + "folderPath": { + "value": "[format('@concat(''{0}/'', variables(''destinationFolder''))', variables('INGESTION'))]", + "type": "Expression" + } + } + } + ] + } + ], + "parameters": { + "folderPath": { + "type": "string" }, - { - "name": "privatelink-queue-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" - } + "fileName": { + "type": "string" } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-ep', parameters('clusterName')))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ingestion_OpenDataInternalScripts", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" + "variables": { + "date": { + "type": "String" }, - "databaseName": { - "value": "Ingestion" + "destinationFolder": { + "type": "String" }, - "scripts": { - "value": { - "OpenDataFunctions_resource_type_1": "[variables('$fxv#0')]", - "OpenDataFunctions_resource_type_2": "[variables('$fxv#1')]", - "OpenDataFunctions_resource_type_3": "[variables('$fxv#2')]", - "OpenDataFunctions_resource_type_4": "[variables('$fxv#3')]", - "OpenDataFunctions_resource_type_5": "[variables('$fxv#4')]" - } + "exportDatasetType": { + "type": "String" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "exportDatasetVersion": { + "type": "String" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "hasNoRows": { + "type": "Boolean" + }, + "hubDataset": { + "type": "String" + }, + "mcaColumnToCheck": { + "type": "String" + }, + "schemaFile": { + "type": "String" + }, + "scope": { + "type": "String" } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" - } - }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } + "annotations": [ + "New export" + ] + }, + "dependsOn": [ + "appRegistration", + "dataFactory::dataset_manifest", + "dataFactory::dataset_msexports", + "dataFactory::dataset_msexports_gzip", + "dataFactory::dataset_msexports_parquet", + "dataFactory::pipeline_ToIngestion", + "schemaFiles" + ] + }, + "dataFactory::pipeline_ToIngestion": { + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ETL_{1}', variables('MSEXPORTS'), variables('INGESTION')))]", + "properties": { + "activities": [ + { + "name": "Get Existing Parquet Files", + "description": "Get the previously ingested files so we can remove any older data. This is necessary to avoid data duplication in reports.", + "type": "GetMetadata", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[format('{0}_files', variables('INGESTION'))]", + "type": "DatasetReference", + "parameters": { + "folderPath": "@pipeline().parameters.destinationFolder" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + { + "name": "Filter Out Current Exports", + "description": "Remove existing files from the current export so those files do not get deleted.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Get Existing Parquet Files", + "dependencyConditions": [ + "Completed" + ] } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", + "type": "Expression" + }, + "condition": { + "value": "[format('@and(endswith(item().name, ''.parquet''), not(startswith(item().name, concat(pipeline().parameters.ingestionId, ''{0}''))))', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "Load Schema Mappings", + "description": "Get schema mapping file to use for the CSV to parquet conversion.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + "dataset": { + "referenceName": "[variables('CONFIG')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@toLower(pipeline().parameters.schemaFile)", + "type": "Expression" + }, + "folderPath": "[format('{0}/schemas', variables('CONFIG'))]" + } } } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ingestion_InitScripts", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" - }, - "databaseName": { - "value": "Ingestion" - }, - "scripts": { - "value": { - "openData": "[variables('$fxv#5')]", - "common": "[variables('$fxv#6')]", - "infra": "[variables('$fxv#7')]", - "rawTables": "[replace(variables('$fxv#8'), '$$rawRetentionInDays$$', string(parameters('rawRetentionInDays')))]" - } - }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" + { + "name": "Failed to Load Schema", + "type": "Fail", + "dependsOn": [ + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to load the ', pipeline().parameters.schemaFile, ' schema file. Please confirm the schema and version are supported for FinOps hubs ingestion. Unsupported files will remain in the msexports container.')", + "type": "Expression" + }, + "errorCode": "SchemaLoadFailed" } }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + { + "name": "Set Additional Columns", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "userProperties": [], + "typeProperties": { + "variableName": "additionalColumns", + "value": { + "value": "@intersection(array(json(concat('[{\"name\":\"x_SourceProvider\",\"value\":\"Microsoft\"},{\"name\":\"x_SourceName\",\"value\":\"Cost Management\"},{\"name\":\"x_SourceType\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"},{\"name\":\"x_SourceVersion\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"}'))), activity('Load Schema Mappings').output.firstRow.additionalColumns)", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "For Each Old File", + "description": "Loop thru each of the existing files from previous exports.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Convert to Parquet", + "dependencyConditions": [ + "Succeeded" + ] }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "activity": "Filter Out Current Exports", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Filter Out Current Exports').output.Value", + "type": "Expression" + }, + "activities": [ + { + "name": "Delete Old Ingested File", + "description": "Delete the previously ingested files from older exports.", + "type": "Delete", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[variables('INGESTION')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@concat(pipeline().parameters.destinationFolder, '/', item().name)", + "type": "Expression" + } + } + }, + "enableLogging": false, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + } + } + } + ] } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]", - "[resourceId('Microsoft.Resources/deployments', 'ingestion_OpenDataInternalScripts')]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ingestion_VersionedScripts", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" }, - "databaseName": { - "value": "Ingestion" - }, - "scripts": { - "value": { - "v1_0": "[variables('$fxv#9')]", - "v1_2": "[variables('$fxv#10')]" + { + "name": "Set Destination Path", + "type": "SetVariable", + "dependsOn": [], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "destinationPath", + "value": { + "value": "[format('@concat(pipeline().parameters.destinationFolder, ''/'', pipeline().parameters.ingestionId, ''{0}'', pipeline().parameters.destinationFile)', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" + } } }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" - }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" + { + "name": "Convert to Parquet", + "description": "[format('Convert CSV to parquet and move the file to the {0} container.', variables('INGESTION'))]", + "type": "Switch", + "dependsOn": [ + { + "activity": "Set Destination Path", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Additional Columns", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "on": { + "value": "@last(array(split(pipeline().parameters.blobPath, '.')))", + "type": "Expression" + }, + "cases": [ + { + "value": "csv", + "activities": [ + { + "name": "Convert CSV File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:10:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false, + "translator": { + "value": "@activity('Load Schema Mappings').output.firstRow.translator", + "type": "Expression" + } + }, + "inputs": [ + { + "referenceName": "[replace(format('{0}', variables('MSEXPORTS')), '-', '_')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('INGESTION')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] + } + ] + }, + { + "value": "gz", + "activities": [ + { + "name": "Convert GZip CSV File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:10:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false, + "translator": { + "value": "@activity('Load Schema Mappings').output.firstRow.translator", + "type": "Expression" + } + }, + "inputs": [ + { + "referenceName": "[format('{0}_gzip', variables('MSEXPORTS'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('INGESTION')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] + } + ] + }, + { + "value": "parquet", + "activities": [ + { + "name": "Move Parquet File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "ParquetSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false + }, + "inputs": [ + { + "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('INGESTION')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] + } + ] + } + ], + "defaultActivities": [ + { + "name": "Unsupported File Type", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to ingest the specified export file because the file type is not supported. File: ', pipeline().parameters.blobPath)", + "type": "Expression" + }, + "errorCode": "UnsupportedExportFileType" + } + } + ] } }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." - } + { + "name": "Read Hub Config", + "description": "Read the hub config to determine if the export should be retained.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('CONFIG')]", + "type": "DatasetReference", + "parameters": { + "fileName": "settings.json", + "folderPath": "[variables('CONFIG')]" + } } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "If Not Retaining Exports", + "description": "If the msexports retention period <= 0, delete the source file. The main reason to keep the source file is to allow for troubleshooting and reprocessing in the future.", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Convert to Parquet", + "dependencyConditions": [ + "Succeeded" + ] }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "activity": "Read Hub Config", + "dependencyConditions": [ + "Completed" + ] } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@lessOrEquals(coalesce(activity('Read Hub Config').output.firstRow.retention.msexports.days, 0), 0)", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Delete Source File", + "description": "Delete the exported data file to keep storage costs down. This file is not referenced by any reporting systems.", + "type": "Delete", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + }, + "enableLogging": false, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + } + } + } + ] } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]", - "[resourceId('Microsoft.Resources/deployments', 'ingestion_InitScripts')]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "hub_InitScripts", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", + } + ], "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" + "blobPath": { + "type": "String" }, - "databaseName": { - "value": "Hub" + "destinationFile": { + "type": "string" }, - "scripts": { - "value": { - "common": "[variables('$fxv#11')]", - "openData": "[variables('$fxv#12')]" - } + "destinationFolder": { + "type": "string" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "ingestionId": { + "type": "string" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" - } + "schemaFile": { + "type": "string" }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." - } - } + "exportDatasetType": { + "type": "string" }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" - } - } - ] + "exportDatasetVersion": { + "type": "string" + } + }, + "variables": { + "additionalColumns": { + "type": "Array" + }, + "destinationPath": { + "type": "String" + } } }, "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", - "[resourceId('Microsoft.Resources/deployments', 'ingestion_InitScripts')]" + "appRegistration", + "dataFactory::dataset_msexports", + "dataFactory::dataset_msexports_gzip", + "dataFactory::dataset_msexports_parquet" ] }, - { + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "dependsOn": [ + "appRegistration" + ] + }, + "appRegistration": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "hub_VersionedScripts", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.Exports_Register", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" - }, - "databaseName": { - "value": "Hub" + "app": { + "value": "[parameters('app')]" }, - "scripts": { - "value": { - "v1_0": "[variables('$fxv#13')]", - "v1_2": "[variables('$fxv#14')]" - } + "version": { + "value": "[variables('finOpsToolkitVersion')]" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "features": { + "value": [ + "Storage", + "DataFactory" + ] }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "storageRoles": { + "value": [ + "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9" + ] } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" + "version": "0.39.26.7824", + "templateHash": "5436870138046688593" } }, - "parameters": { - "clusterName": { - "type": "string", + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "databaseName": { - "type": "string", + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "scripts": { + "_1.IdNameObject": { "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } } }, - "resources": [ + "functions": [ { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } } } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", - "[resourceId('Microsoft.Resources/deployments', 'hub_InitScripts')]", - "[resourceId('Microsoft.Resources/deployments', 'ingestion_VersionedScripts')]" - ] - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "hub_LatestScripts", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[parameters('clusterName')]" - }, - "databaseName": { - "value": "Hub" - }, - "scripts": { - "value": { - "latest": "[variables('$fxv#15')]" - } - }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" - }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9449675944348204569" - } - }, + ], "parameters": { - "clusterName": { - "type": "string", + "app": { + "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." + "description": "Required. FinOps hub app getting deployed." } }, - "databaseName": { + "version": { "type": "string", "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + "description": "Required. Version number of the FinOps hub app." } }, - "scripts": { - "type": "object", + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." } }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, + "storageRoles": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." } }, - "forceUpdateTag": { + "telemetryString": { "type": "string", - "defaultValue": "[utcNow()]", + "defaultValue": "", "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" - } - } - ] - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", - "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", - "[resourceId('Microsoft.Resources/deployments', 'hub_VersionedScripts')]" - ] - } - ], - "outputs": { - "clusterId": { - "type": "string", - "metadata": { - "description": "The resource ID of the cluster." - }, - "value": "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "The ID of the cluster system assigned managed identity." - }, - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15', 'full').identity.principalId]" - }, - "clusterName": { - "type": "string", - "metadata": { - "description": "The name of the cluster." - }, - "value": "[parameters('clusterName')]" - }, - "clusterUri": { - "type": "string", - "metadata": { - "description": "The URI of the cluster." - }, - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15').uri]" - }, - "ingestionDbName": { - "type": "string", - "metadata": { - "description": "The name of the database for data ingestion." - }, - "value": "Ingestion" - }, - "hubDbName": { - "type": "string", - "metadata": { - "description": "The name of the database for queries." - }, - "value": "Hub" - }, - "clusterIngestionCapacity": { - "type": "int", - "metadata": { - "description": "Max ingestion capacity of the cluster." - }, - "value": "[coalesce(tryGet(variables('ingestionCapacity'), parameters('clusterSku')), 1)]" - } - } - } - }, - "dependsOn": [ - "core", - "infrastructure" - ] - }, - "dataFactoryResources": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "dataFactoryResources", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft FinOps hubs', 'Microsoft.FinOpsHubs', 'DataFactory', 'FinOps hub engine', variables('$fxv#1'))]" - }, - "hubName": { - "value": "[parameters('hubName')]" - }, - "dataFactoryName": { - "value": "[reference('core').outputs.dataFactoryName.value]" - }, - "location": { - "value": "[parameters('location')]" - }, - "tags": { - "value": "[reference('core').outputs.publisherTags.value]" - }, - "tagsByResource": { - "value": "[parameters('tagsByResource')]" - }, - "storageAccountName": { - "value": "[reference('core').outputs.storageAccountName.value]" - }, - "exportContainerName": { - "value": "[reference('cmExports').outputs.exportContainer.value]" - }, - "configContainerName": { - "value": "[reference('core').outputs.configContainer.value]" - }, - "ingestionContainerName": { - "value": "[reference('core').outputs.ingestionContainer.value]" - }, - "dataExplorerName": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterName.value))]", - "dataExplorerPrincipalId": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.principalId.value))]", - "dataExplorerIngestionDatabase": "[if(variables('useFabric'), createObject('value', 'Ingestion'), if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.ingestionDbName.value)))]", - "dataExplorerIngestionCapacity": "[if(variables('useFabric'), createObject('value', parameters('fabricCapacityUnits')), if(not(variables('deployDataExplorer')), createObject('value', 1), createObject('value', reference('dataExplorer').outputs.clusterIngestionCapacity.value)))]", - "dataExplorerUri": "[if(variables('useFabric'), createObject('value', parameters('fabricQueryUri')), if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterUri.value)))]", - "dataExplorerId": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterId.value))]", - "enableManagedExports": { - "value": "[parameters('enableManagedExports')]" - }, - "enablePublicAccess": { - "value": "[parameters('enablePublicAccess')]" - }, - "keyVaultName": "[if(empty(parameters('remoteHubStorageKey')), createObject('value', ''), createObject('value', reference('remoteHub').outputs.keyVaultName.value))]", - "remoteHubStorageUri": { - "value": "[parameters('remoteHubStorageUri')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "11163540491967572356" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0}', parameters('app').id)]", + "version": "[parameters('version')]" + } + }, + "resources": [] + } }, - "keyVaultSku": { - "type": "string" + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", + "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + }, + "resources": { + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", + "properties": { + "name": "[parameters('app').storage]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "storageAccount" + ] }, - "networkAddressPrefix": { - "type": "string" + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", + "properties": { + "name": "[parameters('app').keyVault]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "keyVault" + ] }, - "privateRouting": { - "type": "bool" + "dataFactory::managedVirtualNetwork": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "properties": {}, + "dependsOn": [ + "dataFactory" + ] }, - "publisherIsolation": { - "type": "bool" + "dataFactory::managedIntegrationRuntime": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "default", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('app').hub.location]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedVirtualNetwork" + ] }, - "storageInfrastructureEncryption": { - "type": "bool" + "dataFactory::linkedService_keyVault": { + "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "keyVault" + ] }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "dataFactory::linkedService_storageAccount": { + "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "storageAccount" + ] }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "dfsEndpoint" + ] + }, + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('app').hub.options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } + } + }, + "storageRoleAssignments": { + "copy": { + "name": "storageRoleAssignments", + "count": "[length(variables('factoryStorageRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "storageAccount" + ] }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "triggerManagerIdentity": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "dependsOn": [ + "dataFactory" + ] }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "triggerManagerIdentity" + ] }, - "keyVault": { - "type": "string" + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]", + "location": "[parameters('app').hub.location]", + "sku": { + "name": "[parameters('app').hub.options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" }, - "scripts": { - "type": "string" + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "blob" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] }, - "displayName": { - "type": "string" + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" }, - "suffix": { - "type": "string" + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getExportBody": { - "parameters": [ - { - "type": "string", - "name": "exportContainerName" + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('app').hub.options.keyVaultSku]", + "family": "A" + }, + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + } + }, + "dependsOn": [ + "dataFactory" + ] }, - { - "type": "string", - "name": "datasetType" + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} }, - { - "type": "string", - "name": "schemaVersion" + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('app').keyVault)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.keyVault]" + }, + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } + ] + }, + "dependsOn": [ + "keyVault" + ] }, - { - "type": "bool", - "name": "isMonthly" + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", + "getStoragePrivateEndpointConnections", + "keyVault" + ] }, - { - "type": "string", - "name": "exportFormat" + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections", + "keyVault" + ] }, - { - "type": "string", - "name": "compressionMode" + "getStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", + "stopTriggers", + "storageAccount" + ] }, - { - "type": "string", - "name": "partitionData" + "approveStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getStoragePrivateEndpointConnections", + "storageAccount" + ] }, - { - "type": "string", - "name": "dataOverwriteBehavior" + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('app').dataFactory]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } + } + }, + "dependsOn": [ + "appTelemetry", + "dataFactory", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" + ] } - ], - "output": { - "type": "string", - "value": "[format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}\", \"name\": \"@{{variables(''exportName'')}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'))]" - } - }, - "getExportBodyV2": { - "parameters": [ - { - "type": "string", - "name": "exportContainerName" - }, - { - "type": "string", - "name": "datasetType" - }, - { - "type": "string", - "name": "schemaVersion" - }, - { - "type": "bool", - "name": "isMonthly" - }, - { - "type": "string", - "name": "exportFormat" - }, - { - "type": "string", - "name": "compressionMode" - }, - { + }, + "outputs": { + "dataFactoryId": { "type": "string", - "name": "partitionData" + "metadata": { + "description": "Resource ID of the Data Factory instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" }, - { + "keyVaultId": { "type": "string", - "name": "dataOverwriteBehavior" + "metadata": { + "description": "Resource ID of the Key Vault instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" }, - { + "storageAccountId": { "type": "string", - "name": "recommendationScope" + "metadata": { + "description": "Resource ID of the storage account instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, - { + "principalId": { "type": "string", - "name": "recommendationLookbackPeriod" + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" }, - { + "triggerManagerIdentityName": { "type": "string", - "name": "resourceType" + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" } - ], - "output": { - "type": "string", - "value": "[if(equals(toLower(parameters('datasetType')), 'focuscost'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{10}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), if(equals(toLower(parameters('datasetType')), 'reservationdetails'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(or(equals(toLower(parameters('datasetType')), 'pricesheet'), equals(toLower(parameters('datasetType')), 'reservationtransactions')), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}}}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheCurrentMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(equals(toLower(parameters('datasetType')), 'reservationrecommendations'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [ {{ \"name\": \"reservationScope\", \"value\": \"{1}\" }}, {{ \"name\": \"resourceType\", \"value\": \"{2}\" }}, {{ \"name\": \"lookBackPeriod\", \"value\": \"{3}\" }}] }}}}, \"timeframe\": \"{4}\", \"type\": \"{5}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{6}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{7}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{8}\", \"partitionData\": \"{9}\", \"dataOverwriteBehavior\": \"{10}\", \"compressionMode\": \"{11}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{12}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{13}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), parameters('recommendationScope'), parameters('resourceType'), parameters('recommendationLookbackPeriod'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), 'undefined'))))]" - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. Temporary app placeholder for the deployments module." - } - }, - "hubName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub instance." - } - }, - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory instance." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. The name of the Azure Key Vault instance." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. The name of the Azure storage account instance." - } - }, - "exportContainerName": { - "type": "string", - "metadata": { - "description": "Required. The name of the container where Cost Management data is exported." - } - }, - "ingestionContainerName": { - "type": "string", - "metadata": { - "description": "Required. The name of the container where normalized data is ingested." - } - }, - "configContainerName": { - "type": "string", - "metadata": { - "description": "Required. The name of the container where normalized data is ingested." - } - }, - "dataExplorerName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics, if applicable." - } - }, - "dataExplorerId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Resource ID of the Azure Data Explorer cluster to use for advanced analytics, if applicable." - } - }, - "dataExplorerPrincipalId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. ID of the Azure Data Explorer cluster system assigned managed identity, if applicable." - } - }, - "dataExplorerUri": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. URI of the Azure Data Explorer cluster or Microsoft Fabric eventhouse query endpoint to use for advanced analytics, if applicable." - } - }, - "dataExplorerIngestionDatabase": { - "type": "string", - "defaultValue": "Ingestion", - "metadata": { - "description": "Optional. Name of the Azure Data Explorer ingestion database. Default: \"ingestion\"." - } - }, - "dataExplorerIngestionCapacity": { - "type": "int", - "defaultValue": 1, - "metadata": { - "description": "Optional. Azure Data Explorer ingestion capacity or Microsoft Fabric capacity units. Increase for non-dev/trial SKUs. Default: 1" - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." - } - }, - "remoteHubStorageUri": { - "type": "string", - "metadata": { - "description": "Optional. Remote storage account for ingestion dataset." - } - }, - "tags": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to apply to all resources." - } - }, - "tagsByResource": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." - } - }, - "enableManagedExports": { - "type": "bool", - "defaultValue": true, - "metadata": { - "description": "Optional. Enable managed exports where your FinOps hub instance will create and run Cost Management exports on your behalf. Not supported for Microsoft Customer Agreement (MCA) billing profiles. Requires the ability to grant User Access Administrator role to FinOps hubs, which is required to create Cost Management exports. Default: true." - } - }, - "enablePublicAccess": { - "type": "bool", - "metadata": { - "description": "Required. Enable public access." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\n#\r\n$adfParams = @{\r\n ResourceGroupName = $env:DataFactoryResourceGroup\r\n DataFactoryName = $env:DataFactoryName\r\n}\r\n\r\n# Delete old triggers\r\n$triggers = Get-AzDataFactoryV2Trigger @adfParams -ErrorAction SilentlyContinue `\r\n| Where-Object { $_.Name -match '^msexports(_(setup|daily|monthly|extract|FileAdded))?$' }\r\n$DeploymentScriptOutputs[\"stopTriggers\"] = $triggers | Stop-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\r\n$DeploymentScriptOutputs[\"deleteTriggers\"] = $triggers | Remove-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\r\n\r\n# Delete old pipelines\r\n$DeploymentScriptOutputs[\"pipelines\"] = Get-AzDataFactoryV2Pipeline @adfParams -ErrorAction SilentlyContinue `\r\n| Where-Object { $_.Name -match '^(msexports_(backfill|extract|fill|get|run|setup|transform)|config_(BackfillData|ExportData|RunBackfill|RunExports))$' } `\r\n| Remove-AzDataFactoryV2Pipeline -Force -ErrorAction SilentlyContinue\r\n", - "$fxv#1": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nParam(\r\n [switch] $Stop\r\n)\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\nif (-not $Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\n# Loop thru triggers\r\n$env:Triggers.Split('|') `\r\n| ForEach-Object {\r\n $trigger = $_\r\n if ($Stop)\r\n {\r\n Write-Output \"Stopping trigger $trigger...\"\r\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force `\r\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\r\n }\r\n else\r\n {\r\n Write-Output \"Starting trigger $trigger...\"\r\n $triggerOutput = Start-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force\r\n }\r\n if ($triggerOutput)\r\n {\r\n Write-Output \"done...\"\r\n }\r\n else\r\n {\r\n Write-Output \"failed...\"\r\n }\r\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\r\n}\r\n\r\nif ($Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\r\n{\r\n $env:Pipelines.Split('|') `\r\n | ForEach-Object {\r\n Write-Output \"Running the init pipeline...\"\r\n Invoke-AzDataFactoryV2Pipeline `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -PipelineName $_\r\n }\r\n}\r\n", - "$fxv#2": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nParam(\r\n [switch] $Stop\r\n)\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\nif (-not $Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\n# Loop thru triggers\r\n$env:Triggers.Split('|') `\r\n| ForEach-Object {\r\n $trigger = $_\r\n if ($Stop)\r\n {\r\n Write-Output \"Stopping trigger $trigger...\"\r\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force `\r\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\r\n }\r\n else\r\n {\r\n Write-Output \"Starting trigger $trigger...\"\r\n $triggerOutput = Start-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force\r\n }\r\n if ($triggerOutput)\r\n {\r\n Write-Output \"done...\"\r\n }\r\n else\r\n {\r\n Write-Output \"failed...\"\r\n }\r\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\r\n}\r\n\r\nif ($Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\r\n{\r\n $env:Pipelines.Split('|') `\r\n | ForEach-Object {\r\n Write-Output \"Running the init pipeline...\"\r\n Invoke-AzDataFactoryV2Pipeline `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -PipelineName $_\r\n }\r\n}\r\n", - "focusSchemaVersion": "1.0", - "exportSchemaVersion": "2023-05-01", - "reservationDetailsSchemaVersion": "2023-03-01", - "ftkVersion": "12.0", - "ftkReleaseUri": "[if(endsWith(variables('ftkVersion'), '-dev'), 'https://github.com/microsoft/finops-toolkit/releases/latest/download', format('https://github.com/microsoft/finops-toolkit/releases/download/v{0}', variables('ftkVersion')))]", - "exportApiVersion": "2023-07-01-preview", - "hubDataExplorerName": "hubDataExplorer", - "deployDataExplorer": "[not(empty(parameters('dataExplorerId')))]", - "useFabric": "[and(not(variables('deployDataExplorer')), not(empty(parameters('dataExplorerUri'))))]", - "datasetPropsDefault": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().fileName}", - "type": "Expression" - }, - "folderPath": { - "value": "@{dataset().folderPath}", - "type": "Expression" - } - } - }, - "safeExportContainerName": "[replace(format('{0}', parameters('exportContainerName')), '-', '_')]", - "safeIngestionContainerName": "[replace(format('{0}', parameters('ingestionContainerName')), '-', '_')]", - "safeConfigContainerName": "[replace(format('{0}', parameters('configContainerName')), '-', '_')]", - "managedVnetName": "default", - "ingestionIdFileNameSeparator": "__", - "exportManifestAddedTriggerName": "[format('{0}_ManifestAdded', variables('safeExportContainerName'))]", - "ingestionManifestAddedTriggerName": "[format('{0}_ManifestAdded', variables('safeIngestionContainerName'))]", - "updateConfigTriggerName": "[format('{0}_SettingsUpdated', variables('safeConfigContainerName'))]", - "dailyTriggerName": "[format('{0}_DailySchedule', variables('safeConfigContainerName'))]", - "monthlyTriggerName": "[format('{0}_MonthlySchedule', variables('safeConfigContainerName'))]", - "allHubTriggers": [ - "[variables('exportManifestAddedTriggerName')]", - "[variables('ingestionManifestAddedTriggerName')]", - "[variables('updateConfigTriggerName')]", - "[variables('dailyTriggerName')]", - "[variables('monthlyTriggerName')]" - ], - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "storageRbacRoles": "[union(createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'), if(not(parameters('enableManagedExports')), createArray(), createArray('18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')))]", - "adxRbacRoles": [ - "b24988ac-6180-42a0-ab88-20f7382dd24c" - ] - }, - "resources": { - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('dataFactoryName')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('storageAccountName')]" - }, - "keyVault": { - "condition": "[not(empty(parameters('remoteHubStorageUri')))]", - "existing": true, - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('keyVaultName')]" - }, - "dataExplorerCluster": { - "condition": "[variables('deployDataExplorer')]", - "existing": true, - "type": "Microsoft.Kusto/clusters", - "apiVersion": "2023-08-15", - "name": "[parameters('dataExplorerName')]" - }, - "managedVirtualNetwork": { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('managedVnetName'))]", - "properties": {} + } + } + } }, - "managedIntegrationRuntime": { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ManagedIntegrationRuntime')]", + "schemaFiles": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.Exports_Storage.SchemaFiles", "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "[variables('managedVnetName')]", - "type": "ManagedVirtualNetworkReference" + "expressionEvaluationOptions": { + "scope": "inner" }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('location')]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "container": { + "value": "config" + }, + "files": { + "value": { + "schemas/actualcost_c360-2025-04.json": "[variables('$fxv#0')]", + "schemas/amortizedcost_c360-2025-04.json": "[variables('$fxv#1')]", + "schemas/focuscost_1.2.json": "[variables('$fxv#2')]", + "schemas/focuscost_1.2-preview.json": "[variables('$fxv#3')]", + "schemas/focuscost_1.0r2.json": "[variables('$fxv#4')]", + "schemas/focuscost_1.0.json": "[variables('$fxv#5')]", + "schemas/focuscost_1.0-preview(v1).json": "[variables('$fxv#6')]", + "schemas/pricesheet_2023-05-01_ea.json": "[variables('$fxv#7')]", + "schemas/pricesheet_2023-05-01_mca.json": "[variables('$fxv#8')]", + "schemas/reservationdetails_2023-03-01.json": "[variables('$fxv#9')]", + "schemas/reservationrecommendations_2023-05-01_ea.json": "[variables('$fxv#10')]", + "schemas/reservationrecommendations_2023-05-01_mca.json": "[variables('$fxv#11')]", + "schemas/reservationtransactions_2023-05-01_ea.json": "[variables('$fxv#12')]", + "schemas/reservationtransactions_2023-05-01_mca.json": "[variables('$fxv#13')]" + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "7314877606184110283" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app that storage is getting updated for." + } + }, + "container": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage container to create or update." + } + }, + "files": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." + } + }, + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." + } } - } - } - }, - "dependsOn": [ - "managedVirtualNetwork" - ] - }, - "storageManagedPrivateEndpoint": { - "condition": "[not(parameters('enablePublicAccess'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), parameters('storageAccountName'))]", - "properties": { - "name": "[parameters('storageAccountName')]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] - }, - "dependsOn": [ - "managedVirtualNetwork", - "storageAccount" - ] - }, - "keyVaultManagedPrivateEndpoint": { - "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), parameters('keyVaultName'))]", - "properties": { - "name": "[parameters('keyVaultName')]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] - }, - "dependsOn": [ - "keyVault", - "managedVirtualNetwork" - ] - }, - "dataExplorerManagedPrivateEndpoint": { - "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), variables('hubDataExplorerName'))]", - "properties": { - "name": "[variables('hubDataExplorerName')]", - "groupId": "cluster", - "privateLinkResourceId": "[parameters('dataExplorerId')]", - "fqdns": [ - "[parameters('dataExplorerUri')]" - ] - }, - "dependsOn": [ - "managedVirtualNetwork" - ] - }, - "triggerManagerIdentity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('dataFactoryName'))]", - "location": "[parameters('location')]", - "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]" - }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('dataFactoryName'))]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('dataFactoryName'))))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "triggerManagerIdentity" - ] - }, - "factoryIdentityStorageRoleAssignments": { - "copy": { - "name": "factoryIdentityStorageRoleAssignments", - "count": "[length(variables('storageRbacRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), variables('storageRbacRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('storageRbacRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory" - ] - }, - "factoryIdentityDataExplorerRoleAssignments": { - "copy": { - "name": "factoryIdentityDataExplorerRoleAssignments", - "count": "[length(variables('adxRbacRoles'))]" - }, - "condition": "[variables('deployDataExplorer')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Kusto/clusters/{0}', parameters('dataExplorerName'))]", - "name": "[guid(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), variables('adxRbacRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('adxRbacRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory" - ] - }, - "linkedService_keyVault": { - "condition": "[not(empty(parameters('remoteHubStorageUri')))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('keyVaultName'))]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('keyVaultName')), '2023-02-01').vaultUri]" - }, - "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" - }, - "dependsOn": [ - "managedIntegrationRuntime" - ] - }, - "linkedService_storageAccount": { - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('storageAccountName'))]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName')), '2021-08-01').primaryEndpoints.dfs]" - }, - "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" - }, - "dependsOn": [ - "managedIntegrationRuntime" - ] - }, - "linkedService_dataExplorer": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('hubDataExplorerName'))]", - "properties": { - "type": "AzureDataExplorer", - "parameters": { - "database": { - "type": "String", - "defaultValue": "[parameters('dataExplorerIngestionDatabase')]" - } - }, - "typeProperties": { - "endpoint": "[parameters('dataExplorerUri')]", - "database": "@{linkedService().database}", - "tenant": "[reference('dataFactory', '2018-06-01', 'full').identity.tenantId]", - "servicePrincipalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - }, - "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" - }, - "dependsOn": [ - "dataFactory", - "managedIntegrationRuntime" - ] - }, - "linkedService_remoteHubStorage": { - "condition": "[not(empty(parameters('remoteHubStorageUri')))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'remoteHubStorage')]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[parameters('remoteHubStorageUri')]", - "accountKey": { - "type": "AzureKeyVaultSecret", - "store": { - "referenceName": "[parameters('keyVaultName')]", - "type": "LinkedServiceReference" - }, - "secretName": "[format('{0}-storage-key', toLower(parameters('hubName')))]" - } - }, - "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" - }, - "dependsOn": [ - "linkedService_keyVault", - "managedIntegrationRuntime" - ] - }, - "linkedService_ftkRepo": { - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ftkRepo')]", - "properties": { - "parameters": { - "filePath": { - "type": "string" - } - }, - "annotations": [], - "type": "HttpServer", - "typeProperties": { - "url": "@concat('https://github.com/microsoft/finops-toolkit/', linkedService().filePath)", - "enableServerCertificateValidation": true, - "authenticationType": "Anonymous" - }, - "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" - }, - "dependsOn": [ - "managedIntegrationRuntime" - ] - }, - "dataset_config": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeConfigContainerName'))]", - "properties": { - "annotations": [], - "parameters": { - "fileName": { - "type": "String", - "defaultValue": "settings.json" }, - "folderPath": { - "type": "String", - "defaultValue": "[parameters('configContainerName')]" - } - }, - "type": "Json", - "typeProperties": "[variables('datasetPropsDefault')]", - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('storageAccountName')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_storageAccount" - ] - }, - "dataset_manifest": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'manifest')]", - "properties": { - "annotations": [], - "parameters": { - "fileName": { - "type": "String", - "defaultValue": "manifest.json" + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" }, - "folderPath": { - "type": "String", - "defaultValue": "[parameters('exportContainerName')]" - } - }, - "type": "Json", - "typeProperties": "[variables('datasetPropsDefault')]", - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('storageAccountName')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_storageAccount" - ] - }, - "dataset_msexports": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeExportContainerName'))]", - "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" + "resources": { + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "properties": { + "publicAccess": "None", + "metadata": {} + } }, - "fileSystem": "[variables('safeExportContainerName')]" - }, - "columnDelimiter": ",", - "escapeChar": "\"", - "quoteChar": "\"", - "firstRowAsHeader": true - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('storageAccountName')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_storageAccount" - ] - }, - "dataset_msexports_gzip": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_gzip', variables('safeExportContainerName')))]", - "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" + "storageAccount::blobService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]" + }, + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Identity', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_blobManager', parameters('app').storage)]" + }, + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "roles": { + "value": [ + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2980528181281411934" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the identity is associated with." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the user assigned identity." + } + }, + "roleAssignmentResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource access is being granted for." + } + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of RBAC role assignment GUIDs." + } + } + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "location": "[parameters('app').hub.location]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." + }, + "value": "[parameters('identityName')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" + } + } + } + } }, - "fileSystem": "[variables('safeExportContainerName')]" - }, - "columnDelimiter": ",", - "escapeChar": "\"", - "quoteChar": "\"", - "firstRowAsHeader": true, - "compressionCodec": "Gzip" - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('storageAccountName')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_storageAccount" - ] - }, - "dataset_msexports_parquet": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_parquet', variables('safeExportContainerName')))]", - "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" + "uploadFiles": { + "condition": "[variables('hasFiles')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Upload', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[reference('identity').outputs.name.value]" + }, + "environmentVariables": { + "value": [ + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" + }, + { + "name": "files", + "value": "[string(parameters('files'))]" + } + ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } + } + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" }, - "fileSystem": "[variables('safeExportContainerName')]" - } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('storageAccountName')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_storageAccount" - ] - }, - "dataset_ingestion": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeIngestionContainerName'))]", - "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" + "filesUploaded": { + "type": "int", + "metadata": { + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" }, - "fileSystem": "[variables('safeIngestionContainerName')]" - } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[if(empty(parameters('remoteHubStorageUri')), parameters('storageAccountName'), 'remoteHubStorage')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_remoteHubStorage", - "linkedService_storageAccount" - ] - }, - "dataset_ingestion_files": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_files', variables('safeIngestionContainerName')))]", - "properties": { - "annotations": [], - "parameters": { - "folderPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileSystem": "[variables('safeIngestionContainerName')]", - "folderPath": { - "value": "@dataset().folderPath", - "type": "Expression" + "identityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" } } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[if(empty(parameters('remoteHubStorageUri')), parameters('storageAccountName'), 'remoteHubStorage')]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "linkedService_remoteHubStorage", - "linkedService_storageAccount" - ] - }, - "dataset_dataExplorer": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('hubDataExplorerName'))]", - "properties": { - "type": "AzureDataExplorerTable", - "linkedServiceName": { - "parameters": { - "database": "@dataset().database" - }, - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference" - }, - "parameters": { - "database": { - "type": "String", - "defaultValue": "[parameters('dataExplorerIngestionDatabase')]" - }, - "table": { - "type": "String" - } - }, - "typeProperties": { - "table": { - "value": "@dataset().table", - "type": "Expression" - } } }, "dependsOn": [ - "linkedService_dataExplorer" + "appRegistration" ] }, - "dataset_ftkReleaseFile": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ftkReleaseFile')]", + "exportContainer": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.Exports_Storage.ExportContainer", "properties": { - "linkedServiceName": { - "referenceName": "ftkRepo", - "type": "LinkedServiceReference" + "expressionEvaluationOptions": { + "scope": "inner" }, + "mode": "Incremental", "parameters": { - "fileName": { - "type": "string" + "app": { + "value": "[parameters('app')]" }, - "version": { - "type": "string", - "defaultValue": "[variables('ftkVersion')]" + "container": { + "value": "[variables('MSEXPORTS')]" } }, - "annotations": [], - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "HttpServerLocation", - "relativeUrl": { - "value": "@concat('releases/download/v', dataset().version, '/', dataset().fileName)", - "type": "Expression" - } - }, - "columnDelimiter": ",", - "escapeChar": "\\", - "firstRowAsHeader": true, - "quoteChar": "\"" - }, - "schema": [] - }, - "dependsOn": [ - "linkedService_ftkRepo" - ] - }, - "trigger_DailySchedule": { - "condition": "[parameters('enableManagedExports')]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('dailyTriggerName'))]", - "properties": { - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[format('{0}_StartExportProcess', variables('safeConfigContainerName'))]", - "type": "PipelineReference" - }, - "parameters": { - "Recurrence": "Daily" - } - } - ], - "type": "ScheduleTrigger", - "typeProperties": { - "recurrence": { - "frequency": "Hour", - "interval": 24, - "startTime": "2023-01-01T01:01:00", - "timeZone": "[reference('azuretimezones').outputs.Timezone.value]" - } - } - }, - "dependsOn": [ - "azuretimezones", - "pipeline_StartExportProcess", - "stopTriggers" - ] - }, - "trigger_MonthlySchedule": { - "condition": "[parameters('enableManagedExports')]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('monthlyTriggerName'))]", - "properties": { - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[format('{0}_StartExportProcess', variables('safeConfigContainerName'))]", - "type": "PipelineReference" - }, - "parameters": { - "Recurrence": "Monthly" - } - } - ], - "type": "ScheduleTrigger", - "typeProperties": { - "recurrence": { - "frequency": "Month", - "interval": 1, - "startTime": "2023-01-05T01:11:00", - "timeZone": "[reference('azuretimezones').outputs.Timezone.value]", - "schedule": { - "monthDays": [ - 2, - 5, - 19 - ] - } - } - } - }, - "dependsOn": [ - "azuretimezones", - "pipeline_StartExportProcess", - "stopTriggers" - ] - }, - "pipeline_InitializeHub": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_InitializeHub', variables('safeConfigContainerName')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "7314877606184110283" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "formatSettings": { - "type": "JsonReadSettings" + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } } }, - "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", - "type": "DatasetReference" - } - } - }, - { - "name": "Set Version", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - ], - "userProperties": [], - "typeProperties": { - "variableName": "version", - "value": { - "value": "@activity('Get Config').output.firstRow.version", - "type": "Expression" + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - } - }, - { - "name": "Set Scopes", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - ], - "userProperties": [], - "typeProperties": { - "variableName": "scopes", - "value": { - "value": "@string(activity('Get Config').output.firstRow.scopes)", - "type": "Expression" + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } } }, - { - "name": "Set Retention", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app that storage is getting updated for." } - ], - "userProperties": [], - "typeProperties": { - "variableName": "retention", - "value": { - "value": "@string(activity('Get Config').output.firstRow.retention)", - "type": "Expression" + }, + "container": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage container to create or update." + } + }, + "files": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." + } + }, + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." } } }, - { - "name": "Until Capacity Is Available", - "type": "Until", - "dependsOn": [ - { - "activity": "Set Version", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Retention", - "dependencyConditions": [ - "Succeeded" - ] + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" + }, + "resources": { + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "properties": { + "publicAccess": "None", + "metadata": {} } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@equals(variables('tryAgain'), false)", - "type": "Expression" - }, - "activities": [ - { - "name": "Confirm Ingestion Capacity", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + }, + "storageAccount::blobService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]" + }, + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Identity', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" }, - "userProperties": [], - "typeProperties": { - "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", - "commandTimeout": "00:20:00" + "identityName": { + "value": "[format('{0}_blobManager', parameters('app').storage)]" }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "roles": { + "value": [ + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2980528181281411934" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } - } - }, - { - "name": "If Has Capacity", - "type": "IfCondition", - "dependsOn": [ + }, + "functions": [ { - "activity": "Confirm Ingestion Capacity", - "dependencyConditions": [ - "Succeeded" - ] + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } } ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", - "type": "Expression" + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the identity is associated with." + } }, - "ifFalseActivities": [ + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the user assigned identity." + } + }, + "roleAssignmentResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource access is being granted for." + } + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of RBAC role assignment GUIDs." + } + } + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "location": "[parameters('app').hub.location]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" + }, + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." + }, + "value": "[parameters('identityName')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" + } + } + } + } + }, + "uploadFiles": { + "condition": "[variables('hasFiles')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.Upload', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[reference('identity').outputs.name.value]" + }, + "environmentVariables": { + "value": [ { - "name": "Wait for Ingestion", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 15 - } + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" }, { - "name": "Try Again", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait for Ingestion", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false + "name": "files", + "value": "[string(parameters('files'))]" + } + ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": true + "value": { + "type": "string" } } - ], - "ifTrueActivities": [ - { - "name": "Set ingestion policy in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "userProperties": [], - "typeProperties": { - "command": { - "value": "[if(variables('useFabric'), format('.show database {0} policy managed_identity', parameters('dataExplorerIngestionDatabase')), format('.alter-merge database {0} policy managed_identity \"[ {{ ''ObjectId'' : ''{1}'', ''AllowedUsages'' : ''NativeIngestion'' }}]\"', parameters('dataExplorerIngestionDatabase'), parameters('dataExplorerPrincipalId')))]", - "type": "Expression" - }, - "commandTimeout": "00:20:00" + "name": { + "type": "string" }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" - } - } - }, - { - "name": "Save Hub Settings in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Set ingestion policy in ADX", - "dependencyConditions": [ - "Succeeded" - ] + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "command": { - "value": "@concat('.append HubSettingsLog <| print version=\"', variables('version'), '\",scopes=dynamic(', variables('scopes'), '),retention=dynamic(', variables('retention'), ') | extend scopes = iff(isnull(scopes[0]), pack_array(scopes), scopes) | mv-apply scopeObj = scopes on (where isnotempty(scopeObj.scope) | summarize scopes = make_set(scopeObj.scope))')", - "type": "Expression" - }, - "commandTimeout": "00:20:00" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } } } }, - { - "name": "Update PricingUnits in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Save Hub Settings in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace PricingUnits <| externaldata(x_PricingUnitDescription: string, AccountTypes: string, x_PricingBlockSize: decimal, PricingUnit: string)[@\"{0}/PricingUnits.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away AccountTypes', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" - } + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } - }, - { - "name": "Update Regions in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update PricingUnits in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace Regions <| externaldata(ResourceLocation: string, RegionId: string, RegionName: string)[@\"{0}/Regions.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" + "networkName": { + "type": "string" }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } } } }, - { - "name": "Update ResourceTypes in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update Regions in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace ResourceTypes <| externaldata(x_ResourceType: string, SingularDisplayName: string, PluralDisplayName: string, LowerSingularDisplayName: string, LowerPluralDisplayName: string, IsPreview: bool, Description: string, IconUri: string, Links: string)[@\"{0}/ResourceTypes.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away Links', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" - } + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" } }, - { - "name": "Update Services in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update ResourceTypes in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace Services <| externaldata(x_ConsumedService: string, x_ResourceType: string, ServiceName: string, ServiceCategory: string, ServiceSubcategory: string, PublisherName: string, x_PublisherCategory: string, x_Environment: string, x_ServiceModel: string)[@\"{0}/Services.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" + "name": { + "type": "string" }, - "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" - } + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, - { - "name": "Ingestion Complete", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Update Services in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } - ] - } - }, - { - "name": "Abort On Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "If Has Capacity", - "dependencyConditions": [ - "Failed" + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" ] } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false } } - ], - "timeout": "0.02:00:00" + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" + }, + "filesUploaded": { + "type": "int", + "metadata": { + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" + }, + "identityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + } + } + } + }, + "dependsOn": [ + "appRegistration" + ] + }, + "trigger_ExportManifestAdded": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.Exports_ADF.ExportManifestTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('app').dataFactory]" + }, + "triggerName": { + "value": "[format('{0}_ManifestAdded', variables('MSEXPORTS'))]" + }, + "pipelineName": { + "value": "[format('{0}_ExecuteETL', variables('MSEXPORTS'))]" + }, + "pipelineParameters": { + "value": { + "folderPath": "@triggerBody().folderPath", + "fileName": "@triggerBody().fileName" + } + }, + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "storageContainer": { + "value": "[variables('MSEXPORTS')]" + }, + "storagePathEndsWith": { + "value": "manifest.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14264521107451792604" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." + } + }, + "triggerName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory trigger to create or update." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storageContainer": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storagePathStartsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." + } + }, + "storagePathEndsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } + }, + "pipelineName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } + }, + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + } + } + }, + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" + }, + "parameters": "[parameters('pipelineParameters')]" + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] + } + } + } + ] + } + }, + "dependsOn": [ + "appRegistration", + "dataFactory::pipeline_ExecuteExportsETL" + ] + } + }, + "outputs": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Properties of the hub app." + }, + "value": "[parameters('app')]" + }, + "exportContainer": { + "type": "string", + "metadata": { + "description": "Name of the container used for Cost Management exports." + }, + "value": "[reference('exportContainer').outputs.containerName.value]" + }, + "schemaFilesUploaded": { + "type": "int", + "metadata": { + "description": "Number of schema files uploaded." + }, + "value": "[reference('schemaFiles').outputs.filesUploaded.value]" + } + } + } + }, + "dependsOn": [ + "core" + ] + }, + "cmManagedExports": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.ManagedExports", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft.CostManagement', 'ManagedExports')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "15949887161767442453" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getExportBody": { + "parameters": [ + { + "type": "string", + "name": "exportContainerName" + }, + { + "type": "string", + "name": "datasetType" + }, + { + "type": "string", + "name": "schemaVersion" + }, + { + "type": "bool", + "name": "isMonthly" + }, + { + "type": "string", + "name": "exportFormat" + }, + { + "type": "string", + "name": "compressionMode" + }, + { + "type": "string", + "name": "partitionData" + }, + { + "type": "string", + "name": "dataOverwriteBehavior" + } + ], + "output": { + "type": "string", + "value": "[format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}\", \"name\": \"@{{variables(''exportName'')}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'))]" + } + }, + "getExportBodyV2": { + "parameters": [ + { + "type": "string", + "name": "exportContainerName" + }, + { + "type": "string", + "name": "datasetType" + }, + { + "type": "bool", + "name": "isMonthly" + }, + { + "type": "string", + "name": "exportFormat" + }, + { + "type": "string", + "name": "compressionMode" + }, + { + "type": "string", + "name": "partitionData" + }, + { + "type": "string", + "name": "dataOverwriteBehavior" + }, + { + "type": "string", + "name": "recommendationScope" + }, + { + "type": "string", + "name": "recommendationLookbackPeriod" + }, + { + "type": "string", + "name": "resourceType" } - }, + ], + "output": { + "type": "string", + "value": "[if(equals(toLower(parameters('datasetType')), 'focuscost'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{10}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), if(equals(toLower(parameters('datasetType')), 'reservationdetails'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(or(equals(toLower(parameters('datasetType')), 'pricesheet'), equals(toLower(parameters('datasetType')), 'reservationtransactions')), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}}}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheCurrentMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(equals(toLower(parameters('datasetType')), 'reservationrecommendations'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [ {{ \"name\": \"reservationScope\", \"value\": \"{1}\" }}, {{ \"name\": \"resourceType\", \"value\": \"{2}\" }}, {{ \"name\": \"lookBackPeriod\", \"value\": \"{3}\" }}] }}}}, \"timeframe\": \"{4}\", \"type\": \"{5}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{6}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{7}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{8}\", \"partitionData\": \"{9}\", \"dataOverwriteBehavior\": \"{10}\", \"compressionMode\": \"{11}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{12}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{13}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], parameters('recommendationScope'), parameters('resourceType'), parameters('recommendationLookbackPeriod'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), 'undefined'))))]" + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + } + }, + "variables": { + "CONFIG": "config", + "MSEXPORTS": "msexports", + "exportsApiVersion": "2023-07-01-preview", + "exportDataVersions": { + "focuscost": "1.2-preview", + "pricesheet": "2023-03-01", + "reservationdetails": "2023-03-01", + "reservationrecommendations": "2023-05-01", + "reservationtransactions": "2023-05-01" + }, + "finOpsToolkitVersion": "12.0" + }, + "resources": { + "dataFactory::dataset_config": { + "existing": true, + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]" + }, + "dataFactory::trigger_DailySchedule": { + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_DailySchedule', variables('CONFIG')))]", + "properties": { + "pipelines": [ { - "name": "Timeout Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Until Capacity Is Available", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": "Data Explorer ingestion timed out after 2 hours while waiting for available capacity. Please re-run this pipeline to re-attempt ingestion. If you continue to see this error, please report an issue at https://aka.ms/ftk/ideas.", - "errorCode": "DataExplorerIngestionTimeout" + "pipelineReference": { + "referenceName": "[format('{0}_StartExportProcess', variables('CONFIG'))]", + "type": "PipelineReference" + }, + "parameters": { + "Recurrence": "Daily" } } ], - "concurrency": 1, - "variables": { - "version": { - "type": "String" - }, - "scopes": { - "type": "String" - }, - "retention": { - "type": "String" - }, - "tryAgain": { - "type": "Boolean", - "defaultValue": true + "type": "ScheduleTrigger", + "typeProperties": { + "recurrence": { + "frequency": "Hour", + "interval": 24, + "startTime": "2023-01-01T01:01:00", + "timeZone": "[reference('timeZones').outputs.Timezone.value]" } } }, "dependsOn": [ - "dataset_config", - "linkedService_dataExplorer" - ], - "metadata": { - "description": "Initializes the hub instance based on the configuration settings." - } + "dataFactory::pipeline_StartExportProcess", + "timeZones" + ] + }, + "dataFactory::trigger_MonthlySchedule": { + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_MonthlySchedule', variables('CONFIG')))]", + "properties": { + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[format('{0}_StartExportProcess', variables('CONFIG'))]", + "type": "PipelineReference" + }, + "parameters": { + "Recurrence": "Monthly" + } + } + ], + "type": "ScheduleTrigger", + "typeProperties": { + "recurrence": { + "frequency": "Month", + "interval": 1, + "startTime": "2023-01-05T01:11:00", + "timeZone": "[reference('timeZones').outputs.Timezone.value]", + "schedule": { + "monthDays": [ + 2, + 5, + 19 + ] + } + } + } + }, + "dependsOn": [ + "dataFactory::pipeline_StartExportProcess", + "timeZones" + ] }, - "pipeline_StartBackfillProcess": { - "condition": "[parameters('enableManagedExports')]", + "dataFactory::pipeline_StartBackfillProcess": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_StartBackfillProcess', variables('safeConfigContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_StartBackfillProcess', variables('CONFIG')))]", "properties": { "activities": [ { @@ -12061,7 +12908,7 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", + "referenceName": "[variables('CONFIG')]", "type": "DatasetReference", "parameters": { "fileName": { @@ -12227,7 +13074,7 @@ "userProperties": [], "typeProperties": { "pipeline": { - "referenceName": "[format('{0}_RunBackfillJob', variables('safeConfigContainerName'))]", + "referenceName": "[format('{0}_RunBackfillJob', variables('CONFIG'))]", "type": "PipelineReference" }, "waitOnCompletion": true, @@ -12255,11 +13102,11 @@ }, "storageAccountId": { "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, "finOpsHub": { "type": "String", - "defaultValue": "[parameters('hubName')]" + "defaultValue": "[parameters('app').hub.name]" }, "resourceManagementUri": { "type": "String", @@ -12271,7 +13118,7 @@ }, "folderPath": { "type": "String", - "defaultValue": "[parameters('configContainerName')]" + "defaultValue": "[variables('CONFIG')]" }, "endDate": { "type": "String" @@ -12288,18 +13135,13 @@ } }, "dependsOn": [ - "dataset_config", - "pipeline_RunBackfillJob" - ], - "metadata": { - "description": "Runs the backfill job for each month based on retention settings." - } + "dataFactory::pipeline_RunBackfillJob" + ] }, - "pipeline_RunBackfillJob": { - "condition": "[parameters('enableManagedExports')]", + "dataFactory::pipeline_RunBackfillJob": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_RunBackfillJob', variables('safeConfigContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_RunBackfillJob', variables('CONFIG')))]", "properties": { "activities": [ { @@ -12327,7 +13169,7 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", + "referenceName": "[variables('CONFIG')]", "type": "DatasetReference", "parameters": { "fileName": { @@ -12400,7 +13242,8 @@ { "activity": "Set Scopes", "dependencyConditions": [ - "Succeeded" + "Succeeded", + "Failed" ] }, { @@ -12476,14 +13319,14 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}/run?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}/run?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "POST", "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunBackfill@{0}', variables('ftkVersion'))]", + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunBackfill@{0}', variables('finOpsToolkitVersion'))]", "Content-Type": "application/json", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "body": "{\"timePeriod\" : { \"from\" : \"@{pipeline().parameters.StartDate}\", \"to\" : \"@{pipeline().parameters.EndDate}\" }}", "authentication": { @@ -12514,11 +13357,11 @@ }, "storageAccountId": { "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, "finOpsHub": { "type": "String", - "defaultValue": "[parameters('hubName')]" + "defaultValue": "[parameters('app').hub.name]" }, "resourceManagementUri": { "type": "String", @@ -12530,25 +13373,18 @@ }, "folderPath": { "type": "String", - "defaultValue": "[parameters('configContainerName')]" + "defaultValue": "[variables('CONFIG')]" }, "scopesArray": { "type": "Array" } } - }, - "dependsOn": [ - "dataset_config" - ], - "metadata": { - "description": "Creates and triggers exports for all defined scopes for the specified date range." } }, - "pipeline_StartExportProcess": { - "condition": "[parameters('enableManagedExports')]", + "dataFactory::pipeline_StartExportProcess": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_StartExportProcess', variables('safeConfigContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_StartExportProcess', variables('CONFIG')))]", "properties": { "activities": [ { @@ -12576,7 +13412,7 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", + "referenceName": "[variables('CONFIG')]", "type": "DatasetReference", "parameters": { "fileName": { @@ -12649,7 +13485,8 @@ { "activity": "Set Scopes", "dependencyConditions": [ - "Succeeded" + "Succeeded", + "Failed" ] }, { @@ -12705,7 +13542,7 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "GET", @@ -12732,7 +13569,7 @@ "userProperties": [], "typeProperties": { "pipeline": { - "referenceName": "[format('{0}_RunExportJobs', variables('safeConfigContainerName'))]", + "referenceName": "[format('{0}_RunExportJobs', variables('CONFIG'))]", "type": "PipelineReference" }, "waitOnCompletion": true, @@ -12766,11 +13603,11 @@ }, "folderPath": { "type": "String", - "defaultValue": "[parameters('configContainerName')]" + "defaultValue": "[variables('CONFIG')]" }, "finOpsHub": { "type": "String", - "defaultValue": "[parameters('hubName')]" + "defaultValue": "[parameters('app').hub.name]" }, "resourceManagementUri": { "type": "String", @@ -12782,18 +13619,13 @@ } }, "dependsOn": [ - "dataset_config", - "pipeline_RunExportJobs" - ], - "metadata": { - "description": "Gets a list of all Cost Management exports configured for this hub based on the scopes defined in settings.json, then runs each export using the config_RunExportJobs pipeline." - } + "dataFactory::pipeline_RunExportJobs" + ] }, - "pipeline_RunExportJobs": { - "condition": "[parameters('enableManagedExports')]", + "dataFactory::pipeline_RunExportJobs": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_RunExportJobs', variables('safeConfigContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_RunExportJobs', variables('CONFIG')))]", "properties": { "activities": [ { @@ -12834,12 +13666,12 @@ "typeProperties": { "method": "POST", "url": { - "value": "[format('@{{replace(toLower(concat(variables(''resourceManagementUri''),item().id)), ''com//'', ''com/'')}}/run?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{replace(toLower(concat(variables(''resourceManagementUri''),item().id)), ''com//'', ''com/'')}}/run?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "body": " ", "authentication": { @@ -12875,22 +13707,18 @@ }, "hubName": { "type": "String", - "defaultValue": "[parameters('hubName')]" + "defaultValue": "[parameters('app').hub.name]" } } }, "dependsOn": [ - "dataset_config" - ], - "metadata": { - "description": "Runs the specified Cost Management exports." - } + "dataFactory::dataset_config" + ] }, - "pipeline_ConfigureExports": { - "condition": "[parameters('enableManagedExports')]", + "dataFactory::pipeline_ConfigureExports": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ConfigureExports', variables('safeConfigContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ConfigureExports', variables('CONFIG')))]", "properties": { "activities": [ { @@ -12918,7 +13746,7 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", + "referenceName": "[variables('CONFIG')]", "type": "DatasetReference", "parameters": { "fileName": { @@ -12988,7 +13816,8 @@ { "activity": "Save Scopes", "dependencyConditions": [ - "Succeeded" + "Succeeded", + "Failed" ] }, { @@ -13069,7 +13898,7 @@ "value": "ea", "activities": [ { - "name": "EA open month focus export", + "name": "Open month focus export", "type": "WebActivity", "dependsOn": [], "policy": { @@ -13082,17 +13911,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13104,11 +13933,11 @@ } }, { - "name": "EA closed month focus export", + "name": "Closed month focus export", "type": "WebActivity", "dependsOn": [ { - "activity": "EA open month focus export", + "activity": "Open month focus export", "dependencyConditions": [ "Succeeded" ] @@ -13124,17 +13953,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13146,11 +13975,11 @@ } }, { - "name": "EA monthly pricesheet export", + "name": "Monthly pricesheet export", "type": "WebActivity", "dependsOn": [ { - "activity": "EA closed month focus export", + "activity": "Closed month focus export", "dependencyConditions": [ "Succeeded" ] @@ -13166,17 +13995,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'Pricesheet', variables('exportSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'Pricesheet', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13192,7 +14021,7 @@ "type": "WebActivity", "dependsOn": [ { - "activity": "EA monthly pricesheet export", + "activity": "Monthly pricesheet export", "dependencyConditions": [ "Succeeded" ] @@ -13209,12 +14038,12 @@ "typeProperties": { "method": "POST", "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}/run?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}/run?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "body": " ", "authentication": { @@ -13227,11 +14056,11 @@ } }, { - "name": "EA daily reservation details export", + "name": "Daily reservation details export", "type": "WebActivity", "dependsOn": [ { - "activity": "EA monthly pricesheet export", + "activity": "Monthly pricesheet export", "dependencyConditions": [ "Succeeded" ] @@ -13247,17 +14076,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationDetails', variables('reservationDetailsSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationDetails', false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationDetails@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationDetails@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13269,11 +14098,11 @@ } }, { - "name": "EA daily reservation transactions export", + "name": "Daily reservation transactions export", "type": "WebActivity", "dependsOn": [ { - "activity": "EA daily reservation details export", + "activity": "Daily reservation details export", "dependencyConditions": [ "Succeeded" ] @@ -13289,17 +14118,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationtransactions''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationtransactions''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationTransactions', variables('exportSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationTransactions', false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationTransactions@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationTransactions@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13311,11 +14140,11 @@ } }, { - "name": "EA daily shared 30day virtualmachines", + "name": "Daily shared 30day virtual machines", "type": "WebActivity", "dependsOn": [ { - "activity": "EA daily reservation transactions export", + "activity": "Daily reservation transactions export", "dependencyConditions": [ "Succeeded" ] @@ -13331,17 +14160,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-recommendations-shared-last30days-virtualmachines''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-recommendations-shared-last30days-virtualmachines''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationRecommendations', variables('exportSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', 'Shared', 'Last30Days', 'VirtualMachines')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationRecommendations', false(), 'CSV', 'None', 'true', 'CreateNewReport', 'Shared', 'Last30Days', 'VirtualMachines')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationRecommendations.VM.Shared.30d@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationRecommendations.VM.Shared.30d@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13371,17 +14200,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13413,17 +14242,17 @@ "userProperties": [], "typeProperties": { "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", "type": "Expression" }, "method": "PUT", "body": { - "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", "type": "Expression" }, "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('ftkVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('finOpsToolkitVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" }, "authentication": { "type": "MSI", @@ -13454,983 +14283,2779 @@ } ] } - ], - "defaultActivities": [ - { - "name": "Export Type Not Defined Error", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to determine the export scope type for :',variables('exportScope'))", - "type": "Expression" - }, - "errorCode": "ExportTypeNotDefined" + ], + "defaultActivities": [ + { + "name": "Export Type Not Defined Error", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to determine the export scope type for :',variables('exportScope'))", + "type": "Expression" + }, + "errorCode": "ExportTypeNotDefined" + } + } + ] + } + } + ] + } + } + ], + "concurrency": 1, + "variables": { + "scopesArray": { + "type": "Array" + }, + "exportName": { + "type": "String" + }, + "exportScope": { + "type": "String" + }, + "exportScopeType": { + "type": "String" + }, + "storageAccountId": { + "type": "String", + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "finOpsHub": { + "type": "String", + "defaultValue": "[parameters('app').hub.name]" + }, + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" + }, + "fileName": { + "type": "String", + "defaultValue": "settings.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[variables('CONFIG')]" + } + } + } + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]" + }, + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]" + }, + "appRegistration": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.ManagedExports_Register", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "version": { + "value": "[variables('finOpsToolkitVersion')]" + }, + "features": { + "value": [ + "DataFactory" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "5436870138046688593" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + }, + "version": { + "type": "string", + "metadata": { + "description": "Required. Version number of the FinOps hub app." + } + }, + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." + } + }, + "storageRoles": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + } + }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0}', parameters('app').id)]", + "version": "[parameters('version')]" + } + }, + "resources": [] + } + }, + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", + "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + }, + "resources": { + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", + "properties": { + "name": "[parameters('app').storage]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "storageAccount" + ] + }, + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", + "properties": { + "name": "[parameters('app').keyVault]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "keyVault" + ] + }, + "dataFactory::managedVirtualNetwork": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "properties": {}, + "dependsOn": [ + "dataFactory" + ] + }, + "dataFactory::managedIntegrationRuntime": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "default", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('app').hub.location]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedVirtualNetwork" + ] + }, + "dataFactory::linkedService_keyVault": { + "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "keyVault" + ] + }, + "dataFactory::linkedService_storageAccount": { + "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "storageAccount" + ] + }, + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "dfsEndpoint" + ] + }, + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('app').hub.options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } + } + }, + "storageRoleAssignments": { + "copy": { + "name": "storageRoleAssignments", + "count": "[length(variables('factoryStorageRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "storageAccount" + ] + }, + "triggerManagerIdentity": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "dependsOn": [ + "dataFactory" + ] + }, + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "triggerManagerIdentity" + ] + }, + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]", + "location": "[parameters('app').hub.location]", + "sku": { + "name": "[parameters('app').hub.options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" + }, + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + }, + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "blob" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('app').hub.options.keyVaultSku]", + "family": "A" + }, + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + } + }, + "dependsOn": [ + "dataFactory" + ] + }, + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('app').keyVault)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.keyVault]" + }, + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } + ] + }, + "dependsOn": [ + "keyVault" + ] + }, + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" } } - ] + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } } } + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", + "getStoragePrivateEndpointConnections", + "keyVault" ] - } - } - ], - "concurrency": 1, - "variables": { - "scopesArray": { - "type": "Array" - }, - "exportName": { - "type": "String" - }, - "exportScope": { - "type": "String" - }, - "exportScopeType": { - "type": "String" - }, - "storageAccountId": { - "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" - }, - "finOpsHub": { - "type": "String", - "defaultValue": "[parameters('hubName')]" - }, - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[parameters('configContainerName')]" - } - } - }, - "dependsOn": [ - "dataset_config" - ], - "metadata": { - "description": "Creates Cost Management exports for supported scopes." - } - }, - "pipeline_ExecuteExportsETL": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ExecuteETL', variables('safeExportContainerName')))]", - "properties": { - "activities": [ - { - "name": "Wait", - "description": "Files may not be available immediately after being created.", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 60 - } - }, - { - "name": "Read Manifest", - "description": "Load the export manifest to determine the scope, dataset, and date range.", - "type": "Lookup", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Completed" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "formatSettings": { - "type": "JsonReadSettings" + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } } }, - "dataset": { - "referenceName": "manifest", - "type": "DatasetReference", + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections", + "keyVault" + ] + }, + "getStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", "parameters": { - "fileName": { - "value": "@pipeline().parameters.fileName", - "type": "Expression" + "storageAccountName": { + "value": "[parameters('app').storage]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } }, - "folderPath": { - "value": "@pipeline().parameters.folderPath", - "type": "Expression" + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } } } - } - } - }, - { - "name": "Set Has No Rows", - "description": "Check the row count ", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "hasNoRows", - "value": { - "value": "@or(equals(activity('Read Manifest').output.firstRow.blobCount, null), equals(activity('Read Manifest').output.firstRow.blobCount, 0))", - "type": "Expression" - } - } - }, - { - "name": "Set Export Dataset Type", - "description": "Save the dataset type from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "exportDatasetType", - "value": { - "value": "@activity('Read Manifest').output.firstRow.exportConfig.type", - "type": "Expression" - } - } - }, - { - "name": "Set MCA Column", - "description": "Determines if the dataset schema has channel-specific columns and saves the column name that only exists in MCA to determine if it is an MCA dataset.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "mcaColumnToCheck", - "value": { - "value": "@if(contains(createArray('pricesheet', 'reservationtransactions'), toLower(variables('exportDatasetType'))), 'BillingProfileId', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Net Savings', null))", - "type": "Expression" - } - } - }, - { - "name": "Set Export Dataset Version", - "description": "Save the dataset version from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "exportDatasetVersion", - "value": { - "value": "@activity('Read Manifest').output.firstRow.exportConfig.dataVersion", - "type": "Expression" - } - } - }, - { - "name": "Detect Channel", - "description": "Determines what channel this export is from. Switch statement handles the different file types if the mcaColumnToCheck variable is set.", - "type": "Switch", - "dependsOn": [ - { - "activity": "Set Has No Rows", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set MCA Column", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Export Dataset Version", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "on": { - "value": "@if(or(empty(variables('mcaColumnToCheck')), variables('hasNoRows')), 'ignore', last(array(split(activity('Read Manifest').output.firstRow.blobs[0].blobName, '.'))))", - "type": "Expression" }, - "cases": [ - { - "value": "csv", - "activities": [ + "dependsOn": [ + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", + "stopTriggers", + "storageAccount" + ] + }, + "approveStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ { - "name": "Check for MCA Column in CSV", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getStoragePrivateEndpointConnections", + "storageAccount" + ] + }, + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('app').dataFactory]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } } }, - "dataset": { - "referenceName": "[variables('safeExportContainerName')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - { - "name": "Set Schema File with Channel in CSV", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in CSV", - "dependencyConditions": [ - "Succeeded" - ] + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in CSV').output, 'firstRow'), contains(activity('Check for MCA Column in CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } - } - ] - }, - { - "value": "gz", - "activities": [ - { - "name": "Check for MCA Column in Gzip CSV", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" - } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "dataset": { - "referenceName": "[format('{0}_gzip', variables('safeExportContainerName'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" - } - } + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } }, - { - "name": "Set Schema File with Channel in Gzip CSV", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in Gzip CSV", - "dependencyConditions": [ - "Succeeded" - ] + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Gzip CSV').output, 'firstRow'), contains(activity('Check for MCA Column in Gzip CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } } - ] - }, - { - "value": "parquet", - "activities": [ - { - "name": "Check for MCA Column in Parquet", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "ParquetSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" - } - }, - "dataset": { - "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" - } - } - } + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" }, - { - "name": "Set Schema File with Channel for Parquet", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in Parquet", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Parquet').output, 'firstRow'), contains(activity('Check for MCA Column in Parquet').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} } - } - } - ] - } - ], - "defaultActivities": [ - { - "name": "Set Schema File", - "type": "SetVariable", - "dependsOn": [], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), '.json'))", - "type": "Expression" + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } + }, + "dependsOn": [ + "appTelemetry", + "dataFactory", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" ] } }, - { - "name": "Set Scope", - "description": "Save the scope from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] + "outputs": { + "dataFactoryId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Data Factory instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" + }, + "keyVaultId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Key Vault instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + }, + "triggerManagerIdentityName": { + "type": "string", + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + } + } + } + } + }, + "timeZones": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.CostManagement.ManagedExports_TimeZones", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('app').hub.location]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "6509457716792571662" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "scope", - "value": { - "value": "@split(toLower(activity('Read Manifest').output.firstRow.exportConfig.resourceId), '/providers/microsoft.costmanagement/exports/')[0]", - "type": "Expression" + "timezoneobject": { + "type": "object", + "defaultValue": { + "australiaeast": "AUS Eastern Standard Time", + "australiacentral": "AUS Eastern Standard Time", + "australiacentral2": "AUS Eastern Standard Time", + "australiasoutheast": "AUS Eastern Standard Time", + "brazilsouth": "E. South America Standard Time", + "canadacentral": "Central Standard Time", + "canadaeast": "Eastern Standard Time", + "centralindia": "India Standard Time", + "centralus": "Central Standard Time", + "eastasia": "China Standard Time", + "eastus": "Eastern Standard Time", + "eastus2": "Eastern Standard Time", + "francecentral": "W. Europe Standard Time", + "germanynorth": "W. Europe Standard Time", + "germanywestcentral": "W. Europe Standard Time", + "japaneast": "Japan Standard Time", + "japanwest": "Japan Standard Time", + "koreacentral": "Korea Standard Time", + "koreasouth": "Korea Standard Time", + "northcentralus": "Central Standard Time", + "northeurope": "GMT Standard Time", + "norwayeast": "W. Europe Standard Time", + "norwaywest": "W. Europe Standard Time", + "southcentralus": "Central Standard Time", + "southindia": "India Standard Time", + "southeastasia": "Singapore Standard Time", + "switzerlandnorth": "W. Europe Standard Time", + "switzerlandwest": "W. Europe Standard Time", + "uksouth": "GMT Standard Time", + "ukwest": "GMT Standard Time", + "westcentralus": "Central Standard Time", + "westeurope": "W. Europe Standard Time", + "westindia": "India Standard Time", + "westus": "Pacific Standard Time", + "westus2": "Pacific Standard Time" } + }, + "utchrs": { + "type": "string", + "defaultValue": "[utcNow('hh')]" + }, + "utcmins": { + "type": "string", + "defaultValue": "[utcNow('mm')]" + }, + "utcsecs": { + "type": "string", + "defaultValue": "[utcNow('ss')]" } }, - { - "name": "Set Date", - "description": "Save the exported month from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] + "variables": { + "loc": "[toLower(replace(parameters('location'), ' ', ''))]", + "timezone": "[coalesce(tryGet(parameters('timezoneobject'), variables('loc')), 'Universal Coordinated Time')]" + }, + "resources": [], + "outputs": { + "AzureRegion": { + "type": "string", + "value": "[parameters('location')]" + }, + "Timezone": { + "type": "string", + "value": "[variables('timezone')]" + }, + "UtcHours": { + "type": "string", + "value": "[parameters('utchrs')]" + }, + "UtcMinutes": { + "type": "string", + "value": "[parameters('utcmins')]" + }, + "UtcSeconds": { + "type": "string", + "value": "[parameters('utcsecs')]" + } + } + } + } + }, + "trigger_SettingsUpdated": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_SettingsUpdatedTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('app').dataFactory]" + }, + "triggerName": { + "value": "[format('{0}_SettingsUpdated', variables('CONFIG'))]" + }, + "pipelineName": { + "value": "[format('{0}_ConfigureExports', variables('CONFIG'))]" + }, + "pipelineParameters": { + "value": {} + }, + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "storageContainer": { + "value": "[variables('CONFIG')]" + }, + "storagePathEndsWith": { + "value": "settings.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14264521107451792604" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." + } + }, + "triggerName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory trigger to create or update." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storageContainer": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storagePathStartsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "date", - "value": { - "value": "@replace(substring(activity('Read Manifest').output.firstRow.runInfo.startDate, 0, 7), '-', '')", - "type": "Expression" + "storagePathEndsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } + }, + "pipelineName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } + }, + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." } } }, - { - "name": "Failed to Read Manifest", - "type": "Fail", - "dependsOn": [ - { - "activity": "Set Date", - "dependencyConditions": [ - "Failed" - ] - }, - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Failed" - ] - }, - { - "activity": "Set Scope", - "dependencyConditions": [ - "Failed" - ] - }, - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Failed" - ] - }, - { - "activity": "Set Export Dataset Version", - "dependencyConditions": [ - "Failed" - ] - }, - { - "activity": "Detect Channel", - "dependencyConditions": [ - "Failed" - ] + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" + }, + "parameters": "[parameters('pipelineParameters')]" + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] + } } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Failed to read the manifest file for this export run. Manifest path: ', pipeline().parameters.folderPath)", - "type": "Expression" - }, - "errorCode": "ManifestReadFailed" } - }, - { - "name": "Check Schema", - "description": "Verify that the schema file exists in storage.", - "type": "GetMetadata", - "dependsOn": [ - { - "activity": "Set Scope", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Date", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Detect Channel", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + ] + } + }, + "dependsOn": [ + "dataFactory::pipeline_ConfigureExports" + ] + } + } + } + }, + "dependsOn": [ + "cmExports" + ] + }, + "analytics": { + "condition": "[or(variables('useFabric'), variables('useAzureDataExplorer'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'Analytics')]" + }, + "fabricQueryUri": { + "value": "[parameters('fabricQueryUri')]" + }, + "fabricCapacityUnits": { + "value": "[parameters('fabricCapacityUnits')]" + }, + "clusterName": { + "value": "[parameters('dataExplorerName')]" + }, + "clusterSku": { + "value": "[parameters('dataExplorerSku')]" + }, + "clusterCapacity": { + "value": "[parameters('dataExplorerCapacity')]" + }, + "rawRetentionInDays": { + "value": "[parameters('dataExplorerRawRetentionInDays')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "16399190021391778181" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('schemaFile')", - "type": "Expression" - }, - "folderPath": "[format('{0}/schemas', parameters('configContainerName'))]" - } - }, - "fieldList": [ - "exists" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "privateRoutingForLinkedServices": { + "parameters": [ + { + "$ref": "#/definitions/_1.HubProperties", + "name": "hub" } + ], + "output": { + "type": "object", + "value": "[if(parameters('hub').options.privateRouting, createObject('connectVia', createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference')), createObject())]" }, - { - "name": "Schema Not Found", - "type": "Fail", - "dependsOn": [ - { - "activity": "Check Schema", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('The ', variables('schemaFile'), ' schema mapping file was not found. Please confirm version ', variables('exportDatasetVersion'), ' of the ', variables('exportDatasetType'), ' dataset is supported by this version of FinOps hubs. You may need to upgrade to a newer release. To add support for another dataset, you can create a custom mapping file.')", - "type": "Expression" - }, - "errorCode": "SchemaNotFound" + "metadata": { + "description": "Returns an object that represents the properties needed to enable private routing for linked services. Use property expansion (`...value`) to apply to a linkedServices resource.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + }, + "clusterName": { + "type": "string", + "defaultValue": "", + "maxLength": 22, + "metadata": { + "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics. If empty, Azure Data Explorer will not be deployed. Required to use with Power BI if you have more than $2-5M/mo in costs being monitored. Default: \"\" (do not use)." + } + }, + "clusterSku": { + "type": "string", + "defaultValue": "Dev(No SLA)_Standard_E2a_v4", + "allowedValues": [ + "Dev(No SLA)_Standard_E2a_v4", + "Dev(No SLA)_Standard_D11_v2", + "Standard_D11_v2", + "Standard_D12_v2", + "Standard_D13_v2", + "Standard_D14_v2", + "Standard_D16d_v5", + "Standard_D32d_v4", + "Standard_D32d_v5", + "Standard_DS13_v2+1TB_PS", + "Standard_DS13_v2+2TB_PS", + "Standard_DS14_v2+3TB_PS", + "Standard_DS14_v2+4TB_PS", + "Standard_E2a_v4", + "Standard_E2ads_v5", + "Standard_E2d_v4", + "Standard_E2d_v5", + "Standard_E4a_v4", + "Standard_E4ads_v5", + "Standard_E4d_v4", + "Standard_E4d_v5", + "Standard_E8a_v4", + "Standard_E8ads_v5", + "Standard_E8as_v4+1TB_PS", + "Standard_E8as_v4+2TB_PS", + "Standard_E8as_v5+1TB_PS", + "Standard_E8as_v5+2TB_PS", + "Standard_E8d_v4", + "Standard_E8d_v5", + "Standard_E8s_v4+1TB_PS", + "Standard_E8s_v4+2TB_PS", + "Standard_E8s_v5+1TB_PS", + "Standard_E8s_v5+2TB_PS", + "Standard_E16a_v4", + "Standard_E16ads_v5", + "Standard_E16as_v4+3TB_PS", + "Standard_E16as_v4+4TB_PS", + "Standard_E16as_v5+3TB_PS", + "Standard_E16as_v5+4TB_PS", + "Standard_E16d_v4", + "Standard_E16d_v5", + "Standard_E16s_v4+3TB_PS", + "Standard_E16s_v4+4TB_PS", + "Standard_E16s_v5+3TB_PS", + "Standard_E16s_v5+4TB_PS", + "Standard_E64i_v3", + "Standard_E80ids_v4", + "Standard_EC8ads_v5", + "Standard_EC8as_v5+1TB_PS", + "Standard_EC8as_v5+2TB_PS", + "Standard_EC16ads_v5", + "Standard_EC16as_v5+3TB_PS", + "Standard_EC16as_v5+4TB_PS", + "Standard_L4s", + "Standard_L8as_v3", + "Standard_L8s", + "Standard_L8s_v2", + "Standard_L8s_v3", + "Standard_L16as_v3", + "Standard_L16s", + "Standard_L16s_v2", + "Standard_L16s_v3", + "Standard_L32as_v3", + "Standard_L32s_v3" + ], + "metadata": { + "description": "Optional. Name of the Azure Data Explorer SKU. Default: \"Dev(No SLA)_Standard_E2a_v4\"." + } + }, + "clusterCapacity": { + "type": "int", + "defaultValue": 1, + "minValue": 1, + "maxValue": 1000, + "metadata": { + "description": "Optional. Number of nodes to use in the cluster. Allowed values: 1 for the Basic SKU tier and 2-1000 for Standard. Default: 1 for dev/test SKUs, 2 for standard SKUs." + } + }, + "fabricQueryUri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Microsoft Fabric eventhouse query URI. Default: \"\" (do not use)." + } + }, + "fabricCapacityUnits": { + "type": "int", + "defaultValue": 2, + "minValue": 1, + "maxValue": 2048, + "metadata": { + "description": "Optional. Number of capacity units for the Microsoft Fabric capacity. This is the number in your Fabric SKU (e.g., Trial = 1, F2 = 2, F64 = 64). This is used to manage parallelization in data pipelines. If you change capacity, please redeploy the template. Allowed values: 1 for the Fabric trial and 2-2048 based on the assigned Fabric capacity (e.g., F2-F2048). Default: 2." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "rawRetentionInDays": { + "type": "int", + "metadata": { + "description": "Required. Number of days of data to retain in the Data Explorer *_raw tables." + } + } + }, + "variables": { + "$fxv#0": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_1(id: string) {\n dynamic({\n \"arizeai.observabilityeval/organizations\": { \"SingularDisplayName\": \"Azure Native Arize AI Cloud Service\" }\n ,\"astronomer.astro/organizations\": { \"SingularDisplayName\": \"Astro Organization\" }\n ,\"citrix.services/xenappessentials\": { \"SingularDisplayName\": \"Citrix Virtual Apps Essentials\" }\n ,\"citrix.services/xendesktopessentials\": { \"SingularDisplayName\": \"Citrix Virtual Desktops Essentials\" }\n ,\"commvault.contentstore/cloudaccounts\": { \"SingularDisplayName\": \"Commvault Cloud Account\" }\n ,\"commvault.contentstore/cloudaccounts/plans\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts plan\" }\n ,\"commvault.contentstore/cloudaccounts/protectiongroups\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection group\" }\n ,\"commvault.contentstore/cloudaccounts/protectiongroups/protecteditems\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection groups protected item\" }\n ,\"commvault.contentstore/cloudaccounts/storages\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts storage\" }\n ,\"dell.storage/filesystems\": { \"SingularDisplayName\": \"Dell PowerScale\" }\n ,\"dynatrace.observability/monitors\": { \"SingularDisplayName\": \"Dynatrace\" }\n ,\"github.network/networksettings\": { \"SingularDisplayName\": \"GitHub.Network network setting\" }\n ,\"informatica.datamanagement/organizations\": { \"SingularDisplayName\": \"Informatica Organization\" }\n ,\"lambdatest.hyperexecute/organizations\": { \"SingularDisplayName\": \"Azure Native LambdaTest - HyperExecute Cloud Service\" }\n ,\"microsoft.aad/domainservices\": { \"SingularDisplayName\": \"Microsoft Entra Domain Services\" }\n ,\"microsoft.aadiam/diagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.aadiam diagnostic setting\" }\n ,\"microsoft.aadiam/privatelinkforazuread\": { \"SingularDisplayName\": \"Private Link for Microsoft Entra ID\" }\n ,\"microsoft.advisor/advisorscore\": { \"SingularDisplayName\": \"Microsoft.Advisor advisor score\" }\n ,\"microsoft.advisor/assessments\": { \"SingularDisplayName\": \"Microsoft.Advisor assessment\" }\n ,\"microsoft.advisor/configurations\": { \"SingularDisplayName\": \"Microsoft.Advisor configuration\" }\n ,\"microsoft.advisor/generaterecommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor generate recommendation\" }\n ,\"microsoft.advisor/metadata\": { \"SingularDisplayName\": \"Microsoft.Advisor metadata\" }\n ,\"microsoft.advisor/recommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendation\" }\n ,\"microsoft.advisor/recommendations/suppressions\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendations suppression\" }\n ,\"microsoft.advisor/resiliencyreviews\": { \"SingularDisplayName\": \"Microsoft.Advisor resiliency review\" }\n ,\"microsoft.agfoodplatform/farmbeats\": { \"SingularDisplayName\": \"Azure Data Manager for Agriculture\" }\n ,\"microsoft.agfoodplatform/farmbeatsextensiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats extension definition\" }\n ,\"microsoft.agfoodplatform/farmbeatssolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats solution definition\" }\n ,\"microsoft.agricultureplatform/agriservices\": { \"SingularDisplayName\": \"Agriculture data solutions\" }\n ,\"microsoft.akshybrid/agentpools\": { \"SingularDisplayName\": \"Microsoft.AksHybrid agent pool\" }\n ,\"microsoft.akshybrid/provisionedclusters\": { \"SingularDisplayName\": \"Microsoft.AksHybrid provisioned cluster\" }\n ,\"microsoft.akshybrid/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.AksHybrid upgrade profile\" }\n ,\"microsoft.alertsmanagement/actionrules\": { \"SingularDisplayName\": \"Alert processing rule\" }\n ,\"microsoft.alertsmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alert\" }\n ,\"microsoft.alertsmanagement/alerts/enrichments\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alerts enrichment\" }\n ,\"microsoft.alertsmanagement/prometheusrulegroups\": { \"SingularDisplayName\": \"Prometheus rule group\" }\n ,\"microsoft.alertsmanagement/smartdetectoralertrules\": { \"SingularDisplayName\": \"Smart detector alert rule\" }\n ,\"microsoft.alertsmanagement/smartgroups\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement smart group\" }\n ,\"microsoft.alertsmanagement/tenantactivitylogalerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement tenant activity log alert\" }\n ,\"microsoft.all/arcvirtualmachines\": { \"SingularDisplayName\": \"Azure Arc virtual machine\" }\n ,\"microsoft.all/hcivirtualmachines\": { \"SingularDisplayName\": \"Azure Local Virtual Machine - Azure Arc\" }\n ,\"microsoft.all/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.analysisservices/servers\": { \"SingularDisplayName\": \"Analysis Services server\" }\n ,\"microsoft.anybuild/clusters\": { \"SingularDisplayName\": \"AnyBuild cluster\" }\n ,\"microsoft.apicenter/deletedservices\": { \"SingularDisplayName\": \"Microsoft.ApiCenter deleted service\" }\n ,\"microsoft.apicenter/services\": { \"SingularDisplayName\": \"API Center\" }\n ,\"microsoft.apicenter/services/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.apimanagement/gateways\": { \"SingularDisplayName\": \"API Management gateway\" }\n ,\"microsoft.apimanagement/gateways/configconnections\": { \"SingularDisplayName\": \"Microsoft.ApiManagement gateways config connection\" }\n ,\"microsoft.apimanagement/service\": { \"SingularDisplayName\": \"API Management service\" }\n ,\"microsoft.apimanagement/service/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.apisecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.ApiSecurity defender setting\" }\n ,\"microsoft.app/agents\": { \"SingularDisplayName\": \"SRE Agent\" }\n ,\"microsoft.app/builders\": { \"SingularDisplayName\": \"Microsoft.App builder\" }\n ,\"microsoft.app/builders/builds\": { \"SingularDisplayName\": \"Microsoft.App builders build\" }\n ,\"microsoft.app/connectedenvironments\": { \"SingularDisplayName\": \"Container Apps Connected Environment\" }\n ,\"microsoft.app/containerapps\": { \"SingularDisplayName\": \"Container App\" }\n ,\"microsoft.app/jobs\": { \"SingularDisplayName\": \"Container App Job\" }\n ,\"microsoft.app/logicapps\": { \"SingularDisplayName\": \"Logic app\" }\n ,\"microsoft.app/logicapps/workflows\": { \"SingularDisplayName\": \"Logic app workflow\" }\n ,\"microsoft.app/managedenvironments\": { \"SingularDisplayName\": \"Container Apps Environment\" }\n ,\"microsoft.app/sessionpools\": { \"SingularDisplayName\": \"Container App Session Pool\" }\n ,\"microsoft.app/spaces\": { \"SingularDisplayName\": \"App Space\" }\n ,\"microsoft.appassessment/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate project\" }\n ,\"microsoft.appassessment/migrateprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessment\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedapplications\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed application\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed machine\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/machinestoassess\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments machines to asses\" }\n ,\"microsoft.appassessment/migrateprojects/sites\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects site\" }\n ,\"microsoft.appassessment/migrateprojects/sites/applianceconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects sites appliance configuration\" }\n ,\"microsoft.appcomplianceautomation/reports\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation report\" }\n ,\"microsoft.appcomplianceautomation/reports/evidences\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports evidence\" }\n ,\"microsoft.appcomplianceautomation/reports/scopingconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports scoping configuration\" }\n ,\"microsoft.appcomplianceautomation/reports/snapshots\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshot\" }\n ,\"microsoft.appcomplianceautomation/reports/snapshots/controls\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshots control\" }\n ,\"microsoft.appcomplianceautomation/reports/webhooks\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports webhook\" }\n ,\"microsoft.appconfiguration/configurationstores\": { \"SingularDisplayName\": \"App Configuration\" }\n ,\"microsoft.applicationmigration/discoveryhubs\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hub\" }\n ,\"microsoft.applicationmigration/discoveryhubs/applications\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs application\" }\n ,\"microsoft.applicationmigration/discoveryhubs/applications/members\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs applications member\" }\n ,\"microsoft.applicationmigration/pgsqlsites\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsite\" }\n ,\"microsoft.applicationmigration/pgsqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites agent\" }\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqldatabases\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqldatabase\" }\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqlinstances\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqlinstance\" }\n ,\"microsoft.appplatform/spring\": { \"SingularDisplayName\": \"Azure Spring Apps\" }\n ,\"microsoft.appsecurity/appprotectmanagedrulesetmanifests\": { \"SingularDisplayName\": \"Microsoft.AppSecurity app protect managed rule set manifest\" }\n ,\"microsoft.appsecurity/policies\": { \"SingularDisplayName\": \"App Protect Policy\" }\n ,\"microsoft.arc/all\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\n ,\"microsoft.arc/allfairfax\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\n ,\"microsoft.arc/kubernetesresources\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\n ,\"microsoft.arc/kubernetesresourcesfairfax\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\n ,\"microsoft.arcnetworking/arcnwloadbalancers\": { \"SingularDisplayName\": \"Microsoft.ArcNetworking arc nw load balancer\" }\n ,\"microsoft.aszlabhardware/labservers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware labserver\" }\n ,\"microsoft.aszlabhardware/reservations\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservation\" }\n ,\"microsoft.aszlabhardware/reservations/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservations server\" }\n ,\"microsoft.aszlabhardware/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware server\" }\n ,\"microsoft.attestation/attestationproviders\": { \"SingularDisplayName\": \"Attestation provider\" }\n ,\"microsoft.authorization/accessreviewhistorydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review history definition\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definition\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instance\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances/decisions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instances decision\" }\n ,\"microsoft.authorization/accessreviewschedulesettings\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule setting\" }\n ,\"microsoft.authorization/datapolicymanifests\": { \"SingularDisplayName\": \"Microsoft.Authorization data policy manifest\" }\n ,\"microsoft.authorization/denyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization deny assignment\" }\n ,\"microsoft.authorization/locks\": { \"SingularDisplayName\": \"Microsoft.Authorization lock\" }\n ,\"microsoft.authorization/policyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization policy assignment\" }\n ,\"microsoft.authorization/policydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definition\" }\n ,\"microsoft.authorization/policydefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definitions version\" }\n ,\"microsoft.authorization/policyexemptions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy exemption\" }\n ,\"microsoft.authorization/policysetdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definition\" }\n ,\"microsoft.authorization/policysetdefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definitions version\" }\n ,\"microsoft.authorization/privatelinkassociations\": { \"SingularDisplayName\": \"Microsoft.Authorization private link association\" }\n ,\"microsoft.authorization/provideroperations\": { \"SingularDisplayName\": \"Microsoft.Authorization provider operation\" }\n ,\"microsoft.authorization/resourcemanagementprivatelinks\": { \"SingularDisplayName\": \"Resource management private link\" }\n ,\"microsoft.authorization/roleassignmentapprovals\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approval\" }\n ,\"microsoft.authorization/roleassignmentapprovals/stages\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approvals stage\" }\n ,\"microsoft.authorization/roleassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment\" }\n ,\"microsoft.authorization/roleassignmentscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule instance\" }\n ,\"microsoft.authorization/roleassignmentschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule request\" }\n ,\"microsoft.authorization/roleassignmentschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule\" }\n ,\"microsoft.authorization/roledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role definition\" }\n ,\"microsoft.authorization/roleeligibilityscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule instance\" }\n ,\"microsoft.authorization/roleeligibilityschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule request\" }\n ,\"microsoft.authorization/roleeligibilityschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule\" }\n ,\"microsoft.authorization/rolemanagementalertconfigurations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert configuration\" }\n ,\"microsoft.authorization/rolemanagementalertdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert definition\" }\n ,\"microsoft.authorization/rolemanagementalertoperations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert operation\" }\n ,\"microsoft.authorization/rolemanagementalerts\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert\" }\n ,\"microsoft.authorization/rolemanagementalerts/alertincidents\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alerts alert incident\" }\n ,\"microsoft.authorization/rolemanagementpolicies\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy\" }\n ,\"microsoft.authorization/rolemanagementpolicyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy assignment\" }\n ,\"microsoft.automanage/bestpractices\": { \"SingularDisplayName\": \"Microsoft.Automanage best practice\" }\n ,\"microsoft.automanage/bestpractices/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage best practices version\" }\n ,\"microsoft.automanage/configurationprofileassignments\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignment\" }\n ,\"microsoft.automanage/configurationprofileassignments/reports\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignments report\" }\n ,\"microsoft.automanage/configurationprofiles\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile\" }\n ,\"microsoft.automanage/configurationprofiles/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profiles version\" }\n ,\"microsoft.automanage/serviceprincipals\": { \"SingularDisplayName\": \"ServicePrincipals\" }\n ,\"microsoft.automation/automationaccounts\": { \"SingularDisplayName\": \"Automation account\" }\n ,\"microsoft.automation/automationaccounts/hybridrunbookworkergroups\": { \"SingularDisplayName\": \"Automation hybrid worker group\" }\n ,\"microsoft.automation/automationaccounts/runbooks\": { \"SingularDisplayName\": \"Automation runbook\" }\n ,\"microsoft.autonomousdevelopmentplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform account\" }\n ,\"microsoft.autonomousdevelopmentplatform/accounts/datapools\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform accounts data pool\" }\n ,\"microsoft.autonomousdevelopmentplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform workspace\" }\n ,\"microsoft.avs/privateclouds\": { \"SingularDisplayName\": \"Azure VMware Solution private cloud\" }\n ,\"microsoft.awsconnector/accessanalyzeranalyzers\": { \"SingularDisplayName\": \"Access Analyzer Analyzer\" }\n ,\"microsoft.awsconnector/acmcertificatesummaries\": { \"SingularDisplayName\": \"ACM Certificate Summary\" }\n ,\"microsoft.awsconnector/apigatewayrestapis\": { \"SingularDisplayName\": \"Api Gateway Rest Api\" }\n ,\"microsoft.awsconnector/apigatewaystages\": { \"SingularDisplayName\": \"Api Gateway Stage\" }\n ,\"microsoft.awsconnector/applicationautoscalingscalabletargets\": { \"SingularDisplayName\": \"Application Auto Scaling Scalable Target\" }\n ,\"microsoft.awsconnector/appsyncgraphqlapis\": { \"SingularDisplayName\": \"App Sync Graphql Api\" }\n ,\"microsoft.awsconnector/autoscalingautoscalinggroups\": { \"SingularDisplayName\": \"Auto Scaling Auto Scaling Group\" }\n ,\"microsoft.awsconnector/cloudformationstacks\": { \"SingularDisplayName\": \"Cloud Formation Stack\" }\n ,\"microsoft.awsconnector/cloudformationstacksets\": { \"SingularDisplayName\": \"Cloud Formation Stack Set\" }\n ,\"microsoft.awsconnector/cloudfrontdistributions\": { \"SingularDisplayName\": \"Cloud Front Distribution\" }\n ,\"microsoft.awsconnector/cloudtrailtrails\": { \"SingularDisplayName\": \"Cloud Trail Trail\" }\n ,\"microsoft.awsconnector/cloudwatchalarms\": { \"SingularDisplayName\": \"Cloud Watch Alarm\" }\n ,\"microsoft.awsconnector/codebuildprojects\": { \"SingularDisplayName\": \"Code Build Project\" }\n ,\"microsoft.awsconnector/codebuildsourcecredentialsinfos\": { \"SingularDisplayName\": \"Code Build Source Credentials Info\" }\n ,\"microsoft.awsconnector/configserviceconfigurationrecorders\": { \"SingularDisplayName\": \"Config Service Configuration Recorder\" }\n ,\"microsoft.awsconnector/configserviceconfigurationrecorderstatuses\": { \"SingularDisplayName\": \"Config Service Configuration Recorder Status\" }\n ,\"microsoft.awsconnector/configservicedeliverychannels\": { \"SingularDisplayName\": \"Config Service Delivery Channel\" }\n ,\"microsoft.awsconnector/databasemigrationservicereplicationinstances\": { \"SingularDisplayName\": \"Database Migration Service Replication Instance\" }\n ,\"microsoft.awsconnector/daxclusters\": { \"SingularDisplayName\": \"DAX Cluster\" }\n ,\"microsoft.awsconnector/dynamodbcontinuousbackupsdescriptions\": { \"SingularDisplayName\": \"Dynamo DB Continuous Backups Description\" }\n ,\"microsoft.awsconnector/dynamodbtables\": { \"SingularDisplayName\": \"Dynamo DB Table\" }\n ,\"microsoft.awsconnector/ec2accountattributes\": { \"SingularDisplayName\": \"EC2 Account Attribute\" }\n ,\"microsoft.awsconnector/ec2addresses\": { \"SingularDisplayName\": \"EC2 Address\" }\n ,\"microsoft.awsconnector/ec2flowlogs\": { \"SingularDisplayName\": \"EC2 Flow Log\" }\n ,\"microsoft.awsconnector/ec2images\": { \"SingularDisplayName\": \"EC2 Image\" }\n ,\"microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\n ,\"microsoft.awsconnector/ec2instancestatuses\": { \"SingularDisplayName\": \"EC2 Instance Status\" }\n ,\"microsoft.awsconnector/ec2ipams\": { \"SingularDisplayName\": \"EC2 Ipam\" }\n ,\"microsoft.awsconnector/ec2keypairs\": { \"SingularDisplayName\": \"EC2 Key Pair\" }\n ,\"microsoft.awsconnector/ec2networkacls\": { \"SingularDisplayName\": \"EC2 Network Acl\" }\n ,\"microsoft.awsconnector/ec2networkinterfaces\": { \"SingularDisplayName\": \"EC2 Network Interface\" }\n ,\"microsoft.awsconnector/ec2routetables\": { \"SingularDisplayName\": \"EC2 Route Table\" }\n ,\"microsoft.awsconnector/ec2securitygroups\": { \"SingularDisplayName\": \"EC2 Security Group\" }\n ,\"microsoft.awsconnector/ec2snapshots\": { \"SingularDisplayName\": \"EC2 Snapshot\" }\n ,\"microsoft.awsconnector/ec2subnets\": { \"SingularDisplayName\": \"EC2 Subnet\" }\n ,\"microsoft.awsconnector/ec2volumes\": { \"SingularDisplayName\": \"EC2 Volume\" }\n ,\"microsoft.awsconnector/ec2vpcendpoints\": { \"SingularDisplayName\": \"EC2 VPCEndpoint\" }\n ,\"microsoft.awsconnector/ec2vpcpeeringconnections\": { \"SingularDisplayName\": \"EC2 VPCPeering Connection\" }\n ,\"microsoft.awsconnector/ec2vpcs\": { \"SingularDisplayName\": \"EC2 VPC\" }\n ,\"microsoft.awsconnector/ecrimagedetails\": { \"SingularDisplayName\": \"ECR Image Detail\" }\n ,\"microsoft.awsconnector/ecrrepositories\": { \"SingularDisplayName\": \"ECR Repository\" }\n ,\"microsoft.awsconnector/ecsclusters\": { \"SingularDisplayName\": \"ECS Cluster\" }\n ,\"microsoft.awsconnector/ecsservices\": { \"SingularDisplayName\": \"ECS Service\" }\n ,\"microsoft.awsconnector/ecstaskdefinitions\": { \"SingularDisplayName\": \"ECS Task Definition\" }\n ,\"microsoft.awsconnector/efsfilesystems\": { \"SingularDisplayName\": \"EFS File System\" }\n ,\"microsoft.awsconnector/efsmounttargets\": { \"SingularDisplayName\": \"EFS Mount Target\" }\n ,\"microsoft.awsconnector/eksnodegroups\": { \"SingularDisplayName\": \"EKS Nodegroup\" }\n ,\"microsoft.awsconnector/elasticbeanstalkapplications\": { \"SingularDisplayName\": \"Elastic Beanstalk Application\" }\n ,\"microsoft.awsconnector/elasticbeanstalkconfigurationtemplates\": { \"SingularDisplayName\": \"Elastic Beanstalk Configuration Template\" }\n ,\"microsoft.awsconnector/elasticbeanstalkenvironments\": { \"SingularDisplayName\": \"Elastic Beanstalk Environment\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2listeners\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Listener\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2loadbalancers\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Load Balancer\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2targetgroups\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Target Group\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2targethealthdescriptions\": { \"SingularDisplayName\": \"Elastic Load Balancing v2 Target Health Description\" }\n ,\"microsoft.awsconnector/elasticsearchdomains\": { \"SingularDisplayName\": \"Elasticsearch Domain\" }\n ,\"microsoft.awsconnector/emrclusters\": { \"SingularDisplayName\": \"EMR Cluster\" }\n ,\"microsoft.awsconnector/guarddutydetectors\": { \"SingularDisplayName\": \"Guard Duty Detector\" }\n ,\"microsoft.awsconnector/iamaccesskeylastuseds\": { \"SingularDisplayName\": \"IAM Access Key Last Used\" }\n ,\"microsoft.awsconnector/iamaccesskeymetadata\": { \"SingularDisplayName\": \"IAM Access Key Metadata\" }\n ,\"microsoft.awsconnector/iamgroups\": { \"SingularDisplayName\": \"IAM Group\" }\n ,\"microsoft.awsconnector/iaminstanceprofiles\": { \"SingularDisplayName\": \"IAM Instance Profile\" }\n ,\"microsoft.awsconnector/iammanagedpolicies\": { \"SingularDisplayName\": \"IAM Managed Policy\" }\n ,\"microsoft.awsconnector/iammfadevices\": { \"SingularDisplayName\": \"IAM MFADevice\" }\n ,\"microsoft.awsconnector/iampasswordpolicies\": { \"SingularDisplayName\": \"IAM Password Policy\" }\n ,\"microsoft.awsconnector/iampolicyversions\": { \"SingularDisplayName\": \"IAM Policy Version\" }\n ,\"microsoft.awsconnector/iamroles\": { \"SingularDisplayName\": \"IAM Role\" }\n ,\"microsoft.awsconnector/iamservercertificates\": { \"SingularDisplayName\": \"IAM Server Certificate\" }\n ,\"microsoft.awsconnector/iamuserpolicies\": { \"SingularDisplayName\": \"IAM User Policy\" }\n ,\"microsoft.awsconnector/iamvirtualmfadevices\": { \"SingularDisplayName\": \"IAM Virtual MFADevice\" }\n ,\"microsoft.awsconnector/kmsaliases\": { \"SingularDisplayName\": \"KMS Alias\" }\n ,\"microsoft.awsconnector/kmskeys\": { \"SingularDisplayName\": \"KMS Key\" }\n ,\"microsoft.awsconnector/lambdafunctioncodelocations\": { \"SingularDisplayName\": \"Lambda Function Code Location\" }\n ,\"microsoft.awsconnector/lambdafunctionconfigurations\": { \"SingularDisplayName\": \"Microsoft.AwsConnector lambda function configuration\" }\n ,\"microsoft.awsconnector/lambdafunctions\": { \"SingularDisplayName\": \"Lambda Function\" }\n ,\"microsoft.awsconnector/licensemanagerlicenses\": { \"SingularDisplayName\": \"License Manager License\" }\n ,\"microsoft.awsconnector/lightsailbuckets\": { \"SingularDisplayName\": \"Lightsail Bucket\" }\n ,\"microsoft.awsconnector/lightsailinstances\": { \"SingularDisplayName\": \"Lightsail Instance\" }\n ,\"microsoft.awsconnector/logsloggroups\": { \"SingularDisplayName\": \"Logs Log Group\" }\n ,\"microsoft.awsconnector/logslogstreams\": { \"SingularDisplayName\": \"Logs Log Stream\" }\n ,\"microsoft.awsconnector/logsmetricfilters\": { \"SingularDisplayName\": \"Logs Metric Filter\" }\n ,\"microsoft.awsconnector/logssubscriptionfilters\": { \"SingularDisplayName\": \"Logs Subscription Filter\" }\n ,\"microsoft.awsconnector/macie2jobsummaries\": { \"SingularDisplayName\": \"Macie2 Job Summary\" }\n ,\"microsoft.awsconnector/macieallowlists\": { \"SingularDisplayName\": \"Macie Allow List\" }\n ,\"microsoft.awsconnector/networkfirewallfirewallpolicies\": { \"SingularDisplayName\": \"Network Firewall Firewall Policy\" }\n ,\"microsoft.awsconnector/networkfirewallfirewalls\": { \"SingularDisplayName\": \"Network Firewall Firewall\" }\n ,\"microsoft.awsconnector/networkfirewallrulegroups\": { \"SingularDisplayName\": \"Network Firewall Rule Group\" }\n ,\"microsoft.awsconnector/opensearchdomainstatuses\": { \"SingularDisplayName\": \"Open Search Domain Status\" }\n ,\"microsoft.awsconnector/opensearchservicedomains\": { \"SingularDisplayName\": \"Open Search Service Domain\" }\n ,\"microsoft.awsconnector/organizationsaccounts\": { \"SingularDisplayName\": \"Organizations Account\" }\n ,\"microsoft.awsconnector/organizationsorganizations\": { \"SingularDisplayName\": \"Organizations Organization\" }\n ,\"microsoft.awsconnector/rdsdbclusters\": { \"SingularDisplayName\": \"RDS DBCluster\" }\n ,\"microsoft.awsconnector/rdsdbinstances\": { \"SingularDisplayName\": \"RDS DBInstance\" }\n ,\"microsoft.awsconnector/rdsdbsnapshotattributesresults\": { \"SingularDisplayName\": \"RDS DBSnapshot Attributes Result\" }\n ,\"microsoft.awsconnector/rdsdbsnapshots\": { \"SingularDisplayName\": \"RDS DBSnapshot\" }\n ,\"microsoft.awsconnector/rdseventsubscriptions\": { \"SingularDisplayName\": \"RDS Event Subscription\" }\n ,\"microsoft.awsconnector/rdsexporttasks\": { \"SingularDisplayName\": \"RDS Export Task\" }\n ,\"microsoft.awsconnector/redshiftclusterparametergroups\": { \"SingularDisplayName\": \"Redshift Cluster Parameter Group\" }\n ,\"microsoft.awsconnector/redshiftclusters\": { \"SingularDisplayName\": \"Redshift Cluster\" }\n ,\"microsoft.awsconnector/route53domainsdomainsummaries\": { \"SingularDisplayName\": \"Route 53 Domains Domain Summary\" }\n ,\"microsoft.awsconnector/route53hostedzones\": { \"SingularDisplayName\": \"Route53 Hosted Zone\" }\n ,\"microsoft.awsconnector/route53resourcerecordsets\": { \"SingularDisplayName\": \"Route 53 Resource Record Set\" }\n ,\"microsoft.awsconnector/s3accesscontrolpolicies\": { \"SingularDisplayName\": \"S3 Access Control Policy\" }\n ,\"microsoft.awsconnector/s3accesspoints\": { \"SingularDisplayName\": \"S3 Access Point\" }\n ,\"microsoft.awsconnector/s3bucketpolicies\": { \"SingularDisplayName\": \"S3 Bucket Policy\" }\n ,\"microsoft.awsconnector/s3buckets\": { \"SingularDisplayName\": \"S3 Bucket\" }\n ,\"microsoft.awsconnector/s3controlmultiregionaccesspointpolicydocuments\": { \"SingularDisplayName\": \"S3 Control Multi Region Access Point Policy Document\" }\n ,\"microsoft.awsconnector/sagemakerapps\": { \"SingularDisplayName\": \"Sage Maker App\" }\n ,\"microsoft.awsconnector/sagemakerdevices\": { \"SingularDisplayName\": \"Sage Maker Device\" }\n ,\"microsoft.awsconnector/sagemakerimages\": { \"SingularDisplayName\": \"Sage Maker Image\" }\n ,\"microsoft.awsconnector/sagemakernotebookinstancesummaries\": { \"SingularDisplayName\": \"Sage Maker Notebook Instance Summary\" }\n ,\"microsoft.awsconnector/secretsmanagerresourcepolicies\": { \"SingularDisplayName\": \"Secrets Manager Resource Policy\" }\n ,\"microsoft.awsconnector/secretsmanagersecrets\": { \"SingularDisplayName\": \"Secrets Manager Secret\" }\n ,\"microsoft.awsconnector/snssubscriptions\": { \"SingularDisplayName\": \"SNS Subscription\" }\n ,\"microsoft.awsconnector/snstopics\": { \"SingularDisplayName\": \"SNS Topic\" }\n ,\"microsoft.awsconnector/sqsqueues\": { \"SingularDisplayName\": \"SQS Queue\" }\n ,\"microsoft.awsconnector/ssminstanceinformations\": { \"SingularDisplayName\": \"SSM Instance Information\" }\n ,\"microsoft.awsconnector/ssmparameters\": { \"SingularDisplayName\": \"SSM Parameter\" }\n ,\"microsoft.awsconnector/ssmresourcecompliancesummaryitems\": { \"SingularDisplayName\": \"SSM Resource Compliance Summary Item\" }\n ,\"microsoft.awsconnector/wafv2ipsets\": { \"SingularDisplayName\": \"WAFv2 IPSet\" }\n ,\"microsoft.awsconnector/wafv2loggingconfigurations\": { \"SingularDisplayName\": \"WAFv2 Logging Configuration\" }\n ,\"microsoft.awsconnector/wafv2webaclassociations\": { \"SingularDisplayName\": \"WAFv2 Web ACLAssociation\" }\n ,\"microsoft.awsconnector/wafwebaclsummaries\": { \"SingularDisplayName\": \"WAF Web ACLSummary\" }\n ,\"microsoft.azureactivedirectory/b2cdirectories\": { \"SingularDisplayName\": \"B2C tenant\" }\n ,\"microsoft.azureactivedirectory/ciamdirectories\": { \"SingularDisplayName\": \"External Configuration Tenant\" }\n ,\"microsoft.azureactivedirectory/guestusages\": { \"SingularDisplayName\": \"Guest Usage\" }\n ,\"microsoft.azurearcdata/datacontrollers\": { \"SingularDisplayName\": \"Azure Arc data controller\" }\n ,\"microsoft.azurearcdata/mysqlserver\": { \"SingularDisplayName\": \"MySql Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/postgresinstances\": { \"SingularDisplayName\": \"PostgreSQL server ? Azure Arc\" }\n ,\"microsoft.azurearcdata/postgressqlserver\": { \"SingularDisplayName\": \"PostgresSql Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlmanagedinstances\": { \"SingularDisplayName\": \"SQL managed instance - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserveresulicenses\": { \"SingularDisplayName\": \"SQL Server ESU license\" }\n ,\"microsoft.azurearcdata/sqlserverinstances\": { \"SingularDisplayName\": \"SQL Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserverinstances/databases\": { \"SingularDisplayName\": \"SQL Server database - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserverlicenses\": { \"SingularDisplayName\": \"SQL Server License\" }\n ,\"microsoft.azurebusinesscontinuity/deletedunifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity deleted unified protected item\" }\n ,\"microsoft.azurebusinesscontinuity/unifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity unified protected item\" }\n ,\"microsoft.azurecis/aadapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis AAD application\" }\n ,\"microsoft.azurecis/addressrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis address record\" }\n ,\"microsoft.azurecis/autopilotenvironments\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot environment\" }\n ,\"microsoft.azurecis/autopilotmachinefunctions\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot machine function\" }\n ,\"microsoft.azurecis/autopilotsoftwareloadbalancevirtualips\": { \"SingularDisplayName\": \"Microsoft.AzureCis auto pilot software load balance virtual IP\" }\n ,\"microsoft.azurecis/azcopies\": { \"SingularDisplayName\": \"Microsoft.AzureCis az copy\" }\n ,\"microsoft.azurecis/canonicalnamerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis canonical name record\" }\n ,\"microsoft.azurecis/dsmsallowlists\": { \"SingularDisplayName\": \"Microsoft.AzureCis ds msallowlist\" }\n ,\"microsoft.azurecis/dsmscertificates\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms certificate\" }\n ,\"microsoft.azurecis/dsmsrootfolders\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms root folder\" }\n ,\"microsoft.azurecis/dstsapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts application\" }\n ,\"microsoft.azurecis/dstsserviceaccounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service account\" }\n ,\"microsoft.azurecis/dstsserviceclientidentities\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service client identity\" }\n ,\"microsoft.azurecis/genericgenevaactions\": { \"SingularDisplayName\": \"Microsoft.AzureCis generic geneva action\" }\n ,\"microsoft.azurecis/plannedquotas\": { \"SingularDisplayName\": \"Microsoft.AzureCis planned quota\" }\n ,\"microsoft.azurecis/pointerrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis pointer record\" }\n ,\"microsoft.azurecis/publishconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis publish config value\" }\n ,\"microsoft.azurecis/pushagentv2accounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis push agent v2 account\" }\n ,\"microsoft.azurecis/servicerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis service record\" }\n ,\"microsoft.azurecis/sharedconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis shared config value\" }\n ,\"microsoft.azurecloudmetadata/clouds\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata cloud\" }\n ,\"microsoft.azurecloudmetadata/clouds/geographies\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geography\" }\n ,\"microsoft.azurecloudmetadata/clouds/geographies/regions\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geographies region\" }\n ,\"microsoft.azuredatatransfer/connections\": { \"SingularDisplayName\": \"Connection\" }\n ,\"microsoft.azuredatatransfer/connections/flows\": { \"SingularDisplayName\": \"Flow\" }\n ,\"microsoft.azuredatatransfer/pipelines\": { \"SingularDisplayName\": \"Pipeline\" }\n ,\"microsoft.azurefleet/fleets\": { \"SingularDisplayName\": \"Compute Fleet\" }\n ,\"microsoft.azurefleet/fleetscomputehub\": { \"SingularDisplayName\": \"Compute Fleet\" }\n ,\"microsoft.azureimagetestingforlinux/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job\" }\n ,\"microsoft.azureimagetestingforlinux/jobtemplates\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job template\" }\n ,\"microsoft.azurelargeinstance/azurelargeinstances\": { \"SingularDisplayName\": \"Azure Large Instance\" }\n ,\"microsoft.azurelargeinstance/azurelargestorageinstances\": { \"SingularDisplayName\": \"Microsoft.AzureLargeInstance Azure large storage instance\" }\n ,\"microsoft.azurepercept/accounts\": { \"SingularDisplayName\": \"Microsoft.AzurePercept account\" }\n ,\"microsoft.azurepercept/accounts/devices\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts device\" }\n ,\"microsoft.azurepercept/accounts/devices/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts devices sensor\" }\n ,\"microsoft.azurepercept/accounts/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts sensor\" }\n ,\"microsoft.azurepercept/accounts/solutioninstances\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solutioninstance\" }\n ,\"microsoft.azurepercept/accounts/solutions\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solution\" }\n ,\"microsoft.azurepercept/accounts/targets\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts target\" }\n ,\"microsoft.azureplaywrightservice/accounts\": { \"SingularDisplayName\": \"Playwright Testing\" }\n ,\"microsoft.azurescan/scanningaccounts\": { \"SingularDisplayName\": \"ESRP Scan\" }\n ,\"microsoft.azuresphere/catalogs\": { \"SingularDisplayName\": \"Azure Sphere Catalog\" }\n ,\"microsoft.azurespherev2/catalogs\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalog\" }\n ,\"microsoft.azurespherev2/catalogs/artifacts\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs artifact\" }\n ,\"microsoft.azurespherev2/catalogs/certificates\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs certificate\" }\n ,\"microsoft.azurespherev2/catalogs/deviceregistrations\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs device registration\" }\n ,\"microsoft.azurespherev2/catalogs/provisioningpackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs provisioning package\" }\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channel\" }\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels/deployments\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channels deployment\" }\n ,\"microsoft.azurespherev2/catalogs/updatepackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs update package\" }\n ,\"microsoft.azurestack/cloudmanifestfiles\": { \"SingularDisplayName\": \"Microsoft.AzureStack cloud manifest file\" }\n ,\"microsoft.azurestack/linkedsubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack linked subscription\" }\n ,\"microsoft.azurestack/registrations\": { \"SingularDisplayName\": \"Microsoft.AzureStack registration\" }\n ,\"microsoft.azurestack/registrations/customersubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations customer subscription\" }\n ,\"microsoft.azurestack/registrations/products\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations product\" }\n ,\"microsoft.azurestackhci/clusters\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/clusters/updates/updateruns\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/clusters/updatesummaries\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/devicepools\": { \"SingularDisplayName\": \"Azure Stack\" }\n ,\"microsoft.azurestackhci/edgedevices\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge device\" }\n ,\"microsoft.azurestackhci/edgedevices/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge devices job\" }\n ,\"microsoft.azurestackhci/edgemachines\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machine\" }\n ,\"microsoft.azurestackhci/edgemachines/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machines job\" }\n ,\"microsoft.azurestackhci/edgenodepools\": { \"SingularDisplayName\": \"Azure Stack\" }\n ,\"microsoft.azurestackhci/galleryimages\": { \"SingularDisplayName\": \"Azure Local Gallery image\" }\n ,\"microsoft.azurestackhci/logicalnetworks\": { \"SingularDisplayName\": \"Azure Local Logical network\" }\n ,\"microsoft.azurestackhci/marketplacegalleryimages\": { \"SingularDisplayName\": \"Azure Local Marketplace Gallery image\" }\n ,\"microsoft.azurestackhci/networkinterfaces\": { \"SingularDisplayName\": \"Azure Local VM Network Interface\" }\n ,\"microsoft.azurestackhci/networksecuritygroups\": { \"SingularDisplayName\": \"Azure Local Network Security Group\" }\n ,\"microsoft.azurestackhci/networksecuritygroups/securityrules\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI network security groups security rule\" }\n ,\"microsoft.azurestackhci/storagecontainers\": { \"SingularDisplayName\": \"Azure Local Storage path\" }\n ,\"microsoft.azurestackhci/virtualharddisks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual hard disk\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instance\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances guest agent\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.azurestackhci/virtualmachines\": { \"SingularDisplayName\": \"Azure Local virtual machine - Azure Arc\" }\n ,\"microsoft.azurestackhci/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual network\" }\n ,\"microsoft.backupsolutions/vmwareapplications\": { \"SingularDisplayName\": \"Microsoft.BackupSolutions vmware application\" }\n ,\"microsoft.bakeryhybrid/pies\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid py\" }\n ,\"microsoft.bakeryhybrid/pies/nestedresourcetype\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid pies nested resource type\" }\n ,\"microsoft.baremetal/baremetalconnections\": { \"SingularDisplayName\": \"Microsoft.BareMetal bare metal connection\" }\n ,\"microsoft.baremetal/crayservers\": { \"SingularDisplayName\": \"Cray Server\" }\n ,\"microsoft.baremetal/monitoringservers\": { \"SingularDisplayName\": \"Monitoring Server\" }\n ,\"microsoft.baremetal/peeringsettings\": { \"SingularDisplayName\": \"Microsoft.BareMetal peering setting\" }\n ,\"microsoft.baremetalinfrastructure/baremetalinstances\": { \"SingularDisplayName\": \"BareMetal Instance\" }\n ,\"microsoft.baremetalinfrastructure/baremetalstorageinstances\": { \"SingularDisplayName\": \"Microsoft.BareMetalInfrastructure bare metal storage instance\" }\n ,\"microsoft.batch/batchaccounts\": { \"SingularDisplayName\": \"Batch account\" }\n ,\"microsoft.billing/billingaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing account\" }\n ,\"microsoft.billing/billingaccounts/agreements\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts agreement\" }\n ,\"microsoft.billing/billingaccounts/associatedtenants\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts associated tenant\" }\n ,\"microsoft.billing/billingaccounts/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts available balance\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profile\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles available balance\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers transfer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/instructions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles instruction\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice section\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections product\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections transfer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/paymentmethodlinks\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles payment method link\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles policy\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/transactions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles transaction\" }\n ,\"microsoft.billing/billingaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptionaliases\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription aliase\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptions/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscriptions invoice\" }\n ,\"microsoft.billing/billingaccounts/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customer\" }\n ,\"microsoft.billing/billingaccounts/customers/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers billing subscription\" }\n ,\"microsoft.billing/billingaccounts/customers/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers policy\" }\n ,\"microsoft.billing/billingaccounts/customers/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers product\" }\n ,\"microsoft.billing/billingaccounts/departments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts department\" }\n ,\"microsoft.billing/billingaccounts/departments/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/departments/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role definition\" }\n ,\"microsoft.billing/billingaccounts/departments/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments enrollment account\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment account\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role definition\" }\n ,\"microsoft.billing/billingaccounts/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\n ,\"microsoft.billing/billingaccounts/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\n ,\"microsoft.billing/billingaccounts/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice\" }\n ,\"microsoft.billing/billingaccounts/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice section\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections billing subscription\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections product\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections transfer\" }\n ,\"microsoft.billing/billingaccounts/lineofcredit\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts line of credit\" }\n ,\"microsoft.billing/billingaccounts/migrations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts migration\" }\n ,\"microsoft.billing/billingaccounts/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts payment method\" }\n ,\"microsoft.billing/billingaccounts/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts policy\" }\n ,\"microsoft.billing/billingaccounts/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts product\" }\n ,\"microsoft.billing/billingaccounts/reservationorders\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation order\" }\n ,\"microsoft.billing/billingaccounts/reservationorders/reservations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation orders reservation\" }\n ,\"microsoft.billing/billingaccounts/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\n ,\"microsoft.billing/billingaccounts/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\n ,\"microsoft.billing/billingperiods\": { \"SingularDisplayName\": \"Microsoft.Billing billing period\" }\n ,\"microsoft.billing/billingproperty\": { \"SingularDisplayName\": \"Microsoft.Billing billing property\" }\n ,\"microsoft.billing/billingrequests\": { \"SingularDisplayName\": \"Microsoft.Billing billing request\" }\n ,\"microsoft.billing/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing role assignment\" }\n ,\"microsoft.billing/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing role definition\" }\n ,\"microsoft.billing/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing enrollment account\" }\n ,\"microsoft.billing/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing payment method\" }\n ,\"microsoft.billing/policies\": { \"SingularDisplayName\": \"Microsoft.Billing policy\" }\n ,\"microsoft.billing/promotions\": { \"SingularDisplayName\": \"Microsoft.Billing promotion\" }\n ,\"microsoft.billing/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing transfer\" }\n ,\"microsoft.billingbenefits/credits\": { \"SingularDisplayName\": \"Credit\" }\n ,\"microsoft.billingbenefits/discounts\": { \"SingularDisplayName\": \"Discount\" }\n ,\"microsoft.billingbenefits/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\n ,\"microsoft.billingbenefits/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\n ,\"microsoft.billingbenefits/maccs\": { \"SingularDisplayName\": \"Microsoft Azure Consumption Commitment\" }\n ,\"microsoft.billingbenefits/reservationorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits reservation order aliase\" }\n ,\"microsoft.billingbenefits/savingsplanorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits savings plan order aliase\" }\n ,\"microsoft.billingbenefits/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\n ,\"microsoft.billingbenefits/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\n ,\"microsoft.bing/accounts\": { \"SingularDisplayName\": \"Bing Resource\" }\n ,\"microsoft.blockchain/blockchainmembers\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain member\" }\n ,\"microsoft.blockchain/blockchainmembers/transactionnodes\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain members transaction node\" }\n ,\"microsoft.blockchaintokens/tokenservices\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token service\" }\n ,\"microsoft.blockchaintokens/tokenservices/blockchainnetworks\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services blockchain network\" }\n ,\"microsoft.blockchaintokens/tokenservices/groups\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services group\" }\n ,\"microsoft.blockchaintokens/tokenservices/groups/accounts\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services groups account\" }\n ,\"microsoft.blockchaintokens/tokenservices/tokentemplates\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services token template\" }\n ,\"microsoft.bluefin/instances\": { \"SingularDisplayName\": \"Microsoft.Bluefin instance\" }\n ,\"microsoft.bluefin/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances dataset\" }\n ,\"microsoft.bluefin/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances pipeline\" }\n ,\"microsoft.blueprint/blueprintassignments\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint assignment\" }\n ,\"microsoft.blueprint/blueprints\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint\" }\n ,\"microsoft.blueprint/blueprints/artifacts\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints artifact\" }\n ,\"microsoft.blueprint/blueprints/versions\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints version\" }\n ,\"microsoft.botservice/botservices\": { \"SingularDisplayName\": \"Bot Service\" }\n ,\"microsoft.cache/redis\": { \"SingularDisplayName\": \"Redis cache\" }\n ,\"microsoft.cache/redisenterprise\": { \"SingularDisplayName\": \"Azure Managed Redis\" }\n ,\"microsoft.cache/redisenterprise/databases\": { \"SingularDisplayName\": \"Redis Enterprise database\" }\n ,\"microsoft.capacity/reservationorders\": { \"SingularDisplayName\": \"Reservation order\" }\n ,\"microsoft.capacity/reservationorders/reservations\": { \"SingularDisplayName\": \"Reservation\" }\n ,\"microsoft.cascade/sites\": { \"SingularDisplayName\": \"Microsoft.Cascade site\" }\n ,\"microsoft.cdn/cdnwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Content Delivery Network WAF policy\" }\n ,\"microsoft.cdn/edgeactions\": { \"SingularDisplayName\": \"Edge Action\" }\n ,\"microsoft.cdn/profiles\": { \"SingularDisplayName\": \"Front Door and CDN profile\" }\n ,\"microsoft.cdn/profiles/afdendpoints\": { \"SingularDisplayName\": \"Endpoint\" }\n ,\"microsoft.cdn/profiles/afdendpoints/routes\": { \"SingularDisplayName\": \"Route\" }\n ,\"microsoft.cdn/profiles/customdomains\": { \"SingularDisplayName\": \"Custom domain\" }\n ,\"microsoft.cdn/profiles/endpoints\": { \"SingularDisplayName\": \"CDN endpoint\" }\n ,\"microsoft.cdn/profiles/endpoints/customdomains\": { \"SingularDisplayName\": \"CDN custom domain\" }\n ,\"microsoft.cdn/profiles/endpoints/origins\": { \"SingularDisplayName\": \"CDN origin\" }\n ,\"microsoft.cdn/profiles/origingroups\": { \"SingularDisplayName\": \"Origin group\" }\n ,\"microsoft.cdn/profiles/origingroups/origins\": { \"SingularDisplayName\": \"Origin\" }\n ,\"microsoft.cdn/profiles/rulesets\": { \"SingularDisplayName\": \"Rule set\" }\n ,\"microsoft.cdn/profiles/rulesets/rules\": { \"SingularDisplayName\": \"Rule\" }\n ,\"microsoft.cdn/profiles/secrets\": { \"SingularDisplayName\": \"Secret\" }\n ,\"microsoft.cdn/profiles/securitypolicies\": { \"SingularDisplayName\": \"Security policy\" }\n ,\"microsoft.certificateregistration/certificateorders\": { \"SingularDisplayName\": \"App Service certificate\" }\n ,\"microsoft.certify/testsuites\": { \"SingularDisplayName\": \"Microsoft.Certify test suite\" }\n ,\"microsoft.certify/validationjobs\": { \"SingularDisplayName\": \"Microsoft.Certify validation job\" }\n ,\"microsoft.changeanalysis/profile\": { \"SingularDisplayName\": \"Microsoft.ChangeAnalysis profile\" }\n ,\"microsoft.changesafety/changestates\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change state\" }\n ,\"microsoft.changesafety/changestates/stageprogressions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change states stage progression\" }\n ,\"microsoft.changesafety/stagemaps\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety stage map\" }\n ,\"microsoft.changesafety/validations\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validation\" }\n ,\"microsoft.changesafety/validators\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validator\" }\n ,\"microsoft.changesafety/validators/versions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validators version\" }\n ,\"microsoft.chaos/experiments\": { \"SingularDisplayName\": \"Chaos Experiment\" }\n ,\"microsoft.chaos/privateaccesses\": { \"SingularDisplayName\": \"Agent Private Access\" }\n ,\"microsoft.chaos/targets\": { \"SingularDisplayName\": \"Microsoft.Chaos target\" }\n ,\"microsoft.chaos/targets/capabilities\": { \"SingularDisplayName\": \"Microsoft.Chaos targets capability\" }\n ,\"microsoft.classiccompute/domainnames\": { \"SingularDisplayName\": \"Cloud service (classic)\" }\n ,\"microsoft.classiccompute/domainnames/slots/roles\": { \"SingularDisplayName\": \"Cloud service role (classic)\" }\n ,\"microsoft.classiccompute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine (classic)\" }\n ,\"microsoft.classicnetwork/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group (classic)\" }\n ,\"microsoft.classicnetwork/reservedips\": { \"SingularDisplayName\": \"Reserved IP address (classic)\" }\n ,\"microsoft.classicnetwork/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network (classic)\" }\n })[tolower(id)]\n}\n", + "$fxv#1": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_2(id: string) {\n dynamic({\n \"microsoft.classicstorage/storageaccounts\": { \"SingularDisplayName\": \"Storage account (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/disks\": { \"SingularDisplayName\": \"Disk (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/osimages\": { \"SingularDisplayName\": \"OS image (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/vmimages\": { \"SingularDisplayName\": \"VM image (classic)\" }\n ,\"microsoft.cleanroom/cleanrooms\": { \"SingularDisplayName\": \"Microsoft.CleanRoom cleanroom\" }\n ,\"microsoft.cleanroom/collaborations\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaboration\" }\n ,\"microsoft.cleanroom/collaborations/contracts\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaborations contract\" }\n ,\"microsoft.cleanroom/consortiums\": { \"SingularDisplayName\": \"Microsoft.CleanRoom consortium\" }\n ,\"microsoft.cleanroom/microservices\": { \"SingularDisplayName\": \"Microsoft.CleanRoom microservice\" }\n ,\"microsoft.cloud/hubs\": { \"SingularDisplayName\": \"FinOps hub\" }\n ,\"microsoft.clouddeviceplatform/delegatedidentities\": { \"SingularDisplayName\": \"Microsoft.CloudDevicePlatform delegated identity\" }\n ,\"microsoft.cloudhealth/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\n ,\"microsoft.cloudtest/accounts\": { \"SingularDisplayName\": \"CloudTest Account\" }\n ,\"microsoft.cloudtest/buildcaches\": { \"SingularDisplayName\": \"1ES Build Cache\" }\n ,\"microsoft.cloudtest/hostedpools\": { \"SingularDisplayName\": \"1ES Hosted Pool\" }\n ,\"microsoft.cloudtest/images\": { \"SingularDisplayName\": \"1ES Image\" }\n ,\"microsoft.cloudtest/pools\": { \"SingularDisplayName\": \"CloudTest Pool\" }\n ,\"microsoft.clusterstor/nodes\": { \"SingularDisplayName\": \"ClusterStor\" }\n ,\"microsoft.codesigning/codesigningaccounts\": { \"SingularDisplayName\": \"Trusted Signing Account\" }\n ,\"microsoft.codespaces/plans\": { \"SingularDisplayName\": \"Microsoft.Codespaces plan\" }\n ,\"microsoft.cognitiveservices/accounts\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.cognitiveservices/accounts/projects\": { \"SingularDisplayName\": \"Azure AI Foundry project\" }\n ,\"microsoft.cognitiveservices/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plan\" }\n ,\"microsoft.cognitiveservices/commitmentplans/accountassociations\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plans account association\" }\n ,\"microsoft.communication/communicationservices\": { \"SingularDisplayName\": \"Communication Service\" }\n ,\"microsoft.communication/emailservices\": { \"SingularDisplayName\": \"Email Communication Service\" }\n ,\"microsoft.communication/emailservices/domains\": { \"SingularDisplayName\": \"Email Communication Services Domain\" }\n ,\"microsoft.community/communitytrainings\": { \"SingularDisplayName\": \"Community Training\" }\n ,\"microsoft.compositesolutions/compositesolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution definition\" }\n ,\"microsoft.compositesolutions/compositesolutions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution\" }\n ,\"microsoft.compute/availabilitysets\": { \"SingularDisplayName\": \"Availability set\" }\n ,\"microsoft.compute/capacityreservationgroups\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\n ,\"microsoft.compute/capacityreservationgroups/capacityreservations\": { \"SingularDisplayName\": \"Capacity reservation\" }\n ,\"microsoft.compute/capacityreservationgroupscomputehub\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\n ,\"microsoft.compute/cloudservices\": { \"SingularDisplayName\": \"Cloud service (extended support)\" }\n ,\"microsoft.compute/computefleetinstances\": { \"SingularDisplayName\": \"Instance\" }\n ,\"microsoft.compute/computefleetscalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.compute/diskaccesses\": { \"SingularDisplayName\": \"Disk Access\" }\n ,\"microsoft.compute/diskencryptionsets\": { \"SingularDisplayName\": \"Disk Encryption Set\" }\n ,\"microsoft.compute/disks\": { \"SingularDisplayName\": \"Disk\" }\n ,\"microsoft.compute/galleries\": { \"SingularDisplayName\": \"Azure compute gallery\" }\n ,\"microsoft.compute/galleries/applications\": { \"SingularDisplayName\": \"VM application definition\" }\n ,\"microsoft.compute/galleries/applications/versions\": { \"SingularDisplayName\": \"VM application version\" }\n ,\"microsoft.compute/galleries/images\": { \"SingularDisplayName\": \"VM image definition\" }\n ,\"microsoft.compute/galleries/images/versions\": { \"SingularDisplayName\": \"VM image version\" }\n ,\"microsoft.compute/galleries/imagescomputehub\": { \"SingularDisplayName\": \"VM image definition\" }\n ,\"microsoft.compute/hostgroups\": { \"SingularDisplayName\": \"Host group\" }\n ,\"microsoft.compute/hostgroups/hosts\": { \"SingularDisplayName\": \"Host\" }\n ,\"microsoft.compute/hostgroupscomputehub\": { \"SingularDisplayName\": \"Host group\" }\n ,\"microsoft.compute/images\": { \"SingularDisplayName\": \"Image\" }\n ,\"microsoft.compute/imagescomputehub\": { \"SingularDisplayName\": \"Image\" }\n ,\"microsoft.compute/locations/communitygalleries/images\": { \"SingularDisplayName\": \"Community image\" }\n ,\"microsoft.compute/locations/communitygalleries/imagescomputehub\": { \"SingularDisplayName\": \"Community image\" }\n ,\"microsoft.compute/proximityplacementgroups\": { \"SingularDisplayName\": \"Proximity placement group\" }\n ,\"microsoft.compute/proximityplacementgroupscomputehub\": { \"SingularDisplayName\": \"Proximity placement group\" }\n ,\"microsoft.compute/restorepointcollections\": { \"SingularDisplayName\": \"Restore Point Collection\" }\n ,\"microsoft.compute/restorepointcollections/restorepoints\": { \"SingularDisplayName\": \"Restore Point\" }\n ,\"microsoft.compute/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\n ,\"microsoft.compute/sshpublickeys\": { \"SingularDisplayName\": \"SSH key\" }\n ,\"microsoft.compute/standbypoolinstance\": { \"SingularDisplayName\": \"Standby pool\" }\n ,\"microsoft.compute/virtualmachinecomputehub\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.compute/virtualmachineflexinstances\": { \"SingularDisplayName\": \"Instance\" }\n ,\"microsoft.compute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.compute/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.compute/virtualmachinescalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.compute/virtualmachinescalesets/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine scale set instance\" }\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines/networkinterfaces/ipconfigurations/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\n ,\"microsoft.compute/virtualmachinescalesetscomputehub\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.computehub/advisorcost\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisoroperationalexcellence\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorperformance\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorreliability\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorsecurity\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/all\": { \"SingularDisplayName\": \"All resources\" }\n ,\"microsoft.computehub/backup\": { \"SingularDisplayName\": \"Backup job\" }\n ,\"microsoft.computehub/computehubmain\": { \"SingularDisplayName\": \"Compute infrastructure\" }\n ,\"microsoft.computehub/healthevents\": { \"SingularDisplayName\": \"Health events\" }\n ,\"microsoft.computehub/linuxostype\": { \"SingularDisplayName\": \"Linux OS\" }\n ,\"microsoft.computehub/microsoftdefenderfreetrialsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\n ,\"microsoft.computehub/microsoftdefenderstandardsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\n ,\"microsoft.computehub/outages\": { \"SingularDisplayName\": \"Outages\" }\n ,\"microsoft.computehub/powerstatedeallocated\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/powerstaterunning\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/powerstatestopped\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/provisioningstatefailedresources\": { \"SingularDisplayName\": \"Provisioning states\" }\n ,\"microsoft.computehub/provisioningstatesucceededresources\": { \"SingularDisplayName\": \"Provisioning states\" }\n ,\"microsoft.computehub/windowsostype\": { \"SingularDisplayName\": \"Windows OS\" }\n ,\"microsoft.computeschedule/autoactions\": { \"SingularDisplayName\": \"Automatic Action\" }\n ,\"microsoft.computeschedule/autoactions/occurrences\": { \"SingularDisplayName\": \"Microsoft.ComputeSchedule auto actions occurrence\" }\n ,\"microsoft.confidentialledger/ledgers\": { \"SingularDisplayName\": \"Confidential Ledger\" }\n ,\"microsoft.confidentialledger/managedccfs\": { \"SingularDisplayName\": \"Managed CCF App\" }\n ,\"microsoft.confluent/agreements\": { \"SingularDisplayName\": \"Microsoft.Confluent agreement\" }\n ,\"microsoft.confluent/organizations\": { \"SingularDisplayName\": \"Confluent organization\" }\n ,\"microsoft.connectedcache/cachenodes\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\n ,\"microsoft.connectedcache/enterprisecustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\n ,\"microsoft.connectedcache/enterprisemcccustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\n ,\"microsoft.connectedcache/enterprisemcccustomers/enterprisemcccachenodes\": { \"SingularDisplayName\": \"MCC CacheNode for Enterprise\" }\n ,\"microsoft.connectedcache/ispcustomers\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\n ,\"microsoft.connectedcredentials/credentials\": { \"SingularDisplayName\": \"Microsoft.ConnectedCredentials credential\" }\n ,\"microsoft.connectedvehicle/platformaccounts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVehicle platform account\" }\n ,\"microsoft.connectedvmwarevsphere/clusters\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere cluster\" }\n ,\"microsoft.connectedvmwarevsphere/datastores\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere datastore\" }\n ,\"microsoft.connectedvmwarevsphere/hosts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere host\" }\n ,\"microsoft.connectedvmwarevsphere/resourcepools\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere resource pool\" }\n ,\"microsoft.connectedvmwarevsphere/vcenters\": { \"SingularDisplayName\": \"VMware vCenter\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instance\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances guest agent\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachines\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine template\" }\n ,\"microsoft.connectedvmwarevsphere/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual network\" }\n ,\"microsoft.consumption/budgets\": { \"SingularDisplayName\": \"Microsoft.Consumption budget\" }\n ,\"microsoft.consumption/credits\": { \"SingularDisplayName\": \"Microsoft.Consumption credit\" }\n ,\"microsoft.consumption/pricesheets\": { \"SingularDisplayName\": \"Microsoft.Consumption pricesheet\" }\n ,\"microsoft.containerinstance/containergroupprofiles\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profile\" }\n ,\"microsoft.containerinstance/containergroupprofiles/revisions\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profiles revision\" }\n ,\"microsoft.containerinstance/containergroups\": { \"SingularDisplayName\": \"Container instances\" }\n ,\"microsoft.containerinstance/ngroups\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance ngroup\" }\n ,\"microsoft.containerregistry/registries\": { \"SingularDisplayName\": \"Container registry\" }\n ,\"microsoft.containerregistry/registries/replications\": { \"SingularDisplayName\": \"Container registry replication\" }\n ,\"microsoft.containerregistry/registries/scopemaps\": { \"SingularDisplayName\": \"Container registry scope map\" }\n ,\"microsoft.containerregistry/registries/tokens\": { \"SingularDisplayName\": \"Container registry token\" }\n ,\"microsoft.containerregistry/registries/webhooks\": { \"SingularDisplayName\": \"Container registry webhook\" }\n ,\"microsoft.containerservice/fleets\": { \"SingularDisplayName\": \"Kubernetes fleet manager\" }\n ,\"microsoft.containerservice/managedclusters\": { \"SingularDisplayName\": \"Kubernetes service\" }\n ,\"microsoft.containerservice/managedclusters/managednamespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes namespace\" }\n ,\"microsoft.containerservice/managedclusters/namespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\n ,\"microsoft.containerservice/managedclustersnapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService managedclustersnapshot\" }\n ,\"microsoft.containerservice/snapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService snapshot\" }\n ,\"microsoft.containerstorage/pools\": { \"SingularDisplayName\": \"Container storage\" }\n ,\"microsoft.costmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.CostManagement alert\" }\n ,\"microsoft.costmanagement/budgets\": { \"SingularDisplayName\": \"Microsoft.CostManagement budget\" }\n ,\"microsoft.costmanagement/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement cloud connector\" }\n ,\"microsoft.costmanagement/connectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement connector\" }\n ,\"microsoft.costmanagement/costallocationrules\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost allocation rule\" }\n ,\"microsoft.costmanagement/costdetailsoperationresults\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost details operation result\" }\n ,\"microsoft.costmanagement/exports\": { \"SingularDisplayName\": \"Microsoft.CostManagement export\" }\n ,\"microsoft.costmanagement/externalbillingaccounts\": { \"SingularDisplayName\": \"Microsoft.CostManagement external billing account\" }\n ,\"microsoft.costmanagement/externalsubscriptions\": { \"SingularDisplayName\": \"Microsoft.CostManagement external subscription\" }\n ,\"microsoft.costmanagement/markuprules\": { \"SingularDisplayName\": \"Microsoft.CostManagement markup rule\" }\n ,\"microsoft.costmanagement/operationstatus\": { \"SingularDisplayName\": \"Microsoft.CostManagement operation statu\" }\n ,\"microsoft.costmanagement/reportconfigs\": { \"SingularDisplayName\": \"Microsoft.CostManagement reportconfig\" }\n ,\"microsoft.costmanagement/reports\": { \"SingularDisplayName\": \"Microsoft.CostManagement report\" }\n ,\"microsoft.costmanagement/scheduledactions\": { \"SingularDisplayName\": \"Microsoft.CostManagement scheduled action\" }\n ,\"microsoft.costmanagement/settings\": { \"SingularDisplayName\": \"Microsoft.CostManagement setting\" }\n ,\"microsoft.costmanagement/views\": { \"SingularDisplayName\": \"Microsoft.CostManagement view\" }\n ,\"microsoft.customerlockbox/requests\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox request\" }\n ,\"microsoft.customerlockbox/tenantoptedin\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox tenant opted in\" }\n ,\"microsoft.customproviders/associations\": { \"SingularDisplayName\": \"Microsoft.CustomProviders association\" }\n ,\"microsoft.customproviders/resourceproviders\": { \"SingularDisplayName\": \"Microsoft.CustomProviders resource provider\" }\n ,\"microsoft.dashboard/dashboards\": { \"SingularDisplayName\": \"Azure Monitor dashboards with Grafana\" }\n ,\"microsoft.dashboard/grafana\": { \"SingularDisplayName\": \"Azure Managed Grafana\" }\n ,\"microsoft.dataaccelerator/indexclusters\": { \"SingularDisplayName\": \"Microsoft.DataAccelerator index cluster\" }\n ,\"microsoft.databasefleetmanager/fleets\": { \"SingularDisplayName\": \"Database fleet manager\" }\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces\": { \"SingularDisplayName\": \"Fleetspaces\" }\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces/databases\": { \"SingularDisplayName\": \"Fleet managed database\" }\n ,\"microsoft.databasefleetmanager/fleets/tiers\": { \"SingularDisplayName\": \"tier\" }\n ,\"microsoft.databasewatcher/watchers\": { \"SingularDisplayName\": \"Database watcher\" }\n ,\"microsoft.databox/jobs\": { \"SingularDisplayName\": \"Azure Data Box\" }\n ,\"microsoft.databoxedge/databoxedgedevices\": { \"SingularDisplayName\": \"Azure Stack Edge / Data Box Gateway\" }\n ,\"microsoft.databricks/accessconnectors\": { \"SingularDisplayName\": \"Access Connector for Azure Databricks\" }\n ,\"microsoft.databricks/workspaces\": { \"SingularDisplayName\": \"Azure Databricks Service\" }\n ,\"microsoft.datacatalog/catalogs\": { \"SingularDisplayName\": \"Data catalog\" }\n ,\"microsoft.datacollaboration/workspaces\": { \"SingularDisplayName\": \"Project CI\" }\n ,\"microsoft.datadog/agreements\": { \"SingularDisplayName\": \"Microsoft.Datadog agreement\" }\n ,\"microsoft.datadog/monitors\": { \"SingularDisplayName\": \"Datadog\" }\n ,\"microsoft.datadog/subscriptionstatuses\": { \"SingularDisplayName\": \"Microsoft.Datadog subscription statuse\" }\n ,\"microsoft.datafactory/datafactories\": { \"SingularDisplayName\": \"Data factory\" }\n ,\"microsoft.datafactory/factories\": { \"SingularDisplayName\": \"Data factory (V2)\" }\n ,\"microsoft.datafactory/factories/pipelines\": { \"SingularDisplayName\": \"Data Factory pipeline\" }\n ,\"microsoft.datafactory/factories/triggers\": { \"SingularDisplayName\": \"Data Factory trigger\" }\n ,\"microsoft.datalakeanalytics/accounts\": { \"SingularDisplayName\": \"Data Lake Analytics account\" }\n ,\"microsoft.datalakestore/accounts\": { \"SingularDisplayName\": \"Data Lake Storage Gen1\" }\n ,\"microsoft.datamigration/databasemigrations\": { \"SingularDisplayName\": \"Microsoft.DataMigration database migration\" }\n ,\"microsoft.datamigration/migrationservices\": { \"SingularDisplayName\": \"Microsoft.DataMigration migration service\" }\n ,\"microsoft.datamigration/services\": { \"SingularDisplayName\": \"Azure Database Migration Service (classic)\" }\n ,\"microsoft.datamigration/services/projects\": { \"SingularDisplayName\": \"Azure Database Migration Project\" }\n ,\"microsoft.datamigration/sqlmigrationservices\": { \"SingularDisplayName\": \"Azure Database Migration Service\" }\n ,\"microsoft.dataprotection/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\n ,\"microsoft.dataprotection/resourceguards\": { \"SingularDisplayName\": \"Resource Guard\" }\n ,\"microsoft.datareplication/replicationfabrics\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabric\" }\n ,\"microsoft.datareplication/replicationfabrics/fabricagents\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agent\" }\n ,\"microsoft.datareplication/replicationfabrics/fabricagents/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agents operation\" }\n ,\"microsoft.datareplication/replicationfabrics/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics operation\" }\n ,\"microsoft.datareplication/replicationvaults\": { \"SingularDisplayName\": \"Data replication vault\" }\n ,\"microsoft.datareplication/replicationvaults/alertsettings\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults alert setting\" }\n ,\"microsoft.datareplication/replicationvaults/events\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults event\" }\n ,\"microsoft.datareplication/replicationvaults/jobs\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults job\" }\n ,\"microsoft.datareplication/replicationvaults/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults jobs operation\" }\n ,\"microsoft.datareplication/replicationvaults/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults operation\" }\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnectionproxies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection proxy\" }\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection\" }\n ,\"microsoft.datareplication/replicationvaults/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private link resource\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected item\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items operation\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems/recoverypoints\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items recovery point\" }\n ,\"microsoft.datareplication/replicationvaults/replicationextensions\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extension\" }\n ,\"microsoft.datareplication/replicationvaults/replicationextensions/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extensions operation\" }\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policy\" }\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policies operation\" }\n ,\"microsoft.datashare/accounts\": { \"SingularDisplayName\": \"Data Share\" }\n ,\"microsoft.dbformariadb/servers\": { \"SingularDisplayName\": \"Azure Database for MariaDB server\" }\n ,\"microsoft.dbformysql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for MySQL flexible server\" }\n ,\"microsoft.dbformysql/servers\": { \"SingularDisplayName\": \"MySQL server\" }\n ,\"microsoft.dbforpostgresql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for PostgreSQL flexible server\" }\n ,\"microsoft.dbforpostgresql/servergroupsv2\": { \"SingularDisplayName\": \"Azure Cosmos DB for PostgreSQL Cluster\" }\n ,\"microsoft.dbforpostgresql/servers\": { \"SingularDisplayName\": \"PostgreSQL server\" }\n ,\"microsoft.delegatednetwork/controller\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork controller\" }\n ,\"microsoft.delegatednetwork/delegatedsubnets\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork delegated subnet\" }\n ,\"microsoft.delegatednetwork/orchestrators\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork orchestrator\" }\n ,\"microsoft.dependencymap/maps\": { \"SingularDisplayName\": \"Microsoft.DependencyMap map\" }\n ,\"microsoft.dependencymap/maps/discoverysources\": { \"SingularDisplayName\": \"Microsoft.DependencyMap maps discovery source\" }\n ,\"microsoft.deploymentmanager/artifactsources\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager artifact source\" }\n ,\"microsoft.deploymentmanager/rollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.deploymentmanager/servicetopologies\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topology\" }\n ,\"microsoft.deploymentmanager/servicetopologies/services\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies service\" }\n ,\"microsoft.deploymentmanager/servicetopologies/services/serviceunits\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies services service unit\" }\n ,\"microsoft.deploymentmanager/steps\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager step\" }\n ,\"microsoft.desktopvirtualization/appattachpackages\": { \"SingularDisplayName\": \"App attach package\" }\n ,\"microsoft.desktopvirtualization/applicationgroups\": { \"SingularDisplayName\": \"Application group\" }\n ,\"microsoft.desktopvirtualization/hostpools\": { \"SingularDisplayName\": \"Host pool\" }\n ,\"microsoft.desktopvirtualization/scalingplans\": { \"SingularDisplayName\": \"Scaling plan\" }\n ,\"microsoft.desktopvirtualization/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.devai/instances\": { \"SingularDisplayName\": \"Microsoft.DevAI instance\" }\n ,\"microsoft.devai/instances/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances experiment\" }\n ,\"microsoft.devai/instances/sandboxes\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandbox\" }\n ,\"microsoft.devai/instances/sandboxes/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandboxes experiment\" }\n ,\"microsoft.devcenter/devcenters\": { \"SingularDisplayName\": \"Dev center\" }\n ,\"microsoft.devcenter/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Dev Box definition\" }\n ,\"microsoft.devcenter/networkconnections\": { \"SingularDisplayName\": \"Network connection\" }\n ,\"microsoft.devcenter/plans\": { \"SingularDisplayName\": \"Dev center plan\" }\n ,\"microsoft.devcenter/projects\": { \"SingularDisplayName\": \"Project\" }\n ,\"microsoft.devcenter/projects/pools\": { \"SingularDisplayName\": \"Pool\" }\n ,\"microsoft.developmentwindows365/developmentcloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.DevelopmentWindows365 development cloud pc delegated msi\" }\n ,\"microsoft.devhub/iacprofiles\": { \"SingularDisplayName\": \"Infrastructure as Code Automation\" }\n ,\"microsoft.devhub/templates\": { \"SingularDisplayName\": \"Microsoft.DevHub template\" }\n ,\"microsoft.devhub/templates/versions\": { \"SingularDisplayName\": \"Microsoft.DevHub templates version\" }\n ,\"microsoft.devhub/workflows\": { \"SingularDisplayName\": \"Microsoft.DevHub workflow\" }\n ,\"microsoft.deviceonboarding/discoveryservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery service\" }\n ,\"microsoft.deviceonboarding/discoveryservices/ownershipvoucherpublickeys\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery services ownership voucher public key\" }\n ,\"microsoft.deviceonboarding/onboardingservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding service\" }\n ,\"microsoft.deviceonboarding/onboardingservices/policies\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding services policy\" }\n ,\"microsoft.deviceregistry/assetendpointprofiles\": { \"SingularDisplayName\": \"IoT Asset Endpoint Profile\" }\n ,\"microsoft.deviceregistry/assets\": { \"SingularDisplayName\": \"IoT Asset\" }\n ,\"microsoft.deviceregistry/billingcontainers\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry billing container\" }\n ,\"microsoft.deviceregistry/devices\": { \"SingularDisplayName\": \"IoT Device\" }\n ,\"microsoft.deviceregistry/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset endpoint profile\" }\n ,\"microsoft.deviceregistry/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset\" }\n ,\"microsoft.deviceregistry/namespaces\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespace\" }\n ,\"microsoft.deviceregistry/namespaces/assetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset endpoint profile\" }\n ,\"microsoft.deviceregistry/namespaces/assets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset\" }\n ,\"microsoft.deviceregistry/namespaces/devices\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces device\" }\n ,\"microsoft.deviceregistry/namespaces/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset endpoint profile\" }\n ,\"microsoft.deviceregistry/namespaces/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset\" }\n ,\"microsoft.deviceregistry/schemaregistries\": { \"SingularDisplayName\": \"IoT Schema Registry\" }\n ,\"microsoft.deviceregistry/schemaregistries/schemas\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schema\" }\n ,\"microsoft.deviceregistry/schemaregistries/schemas/schemaversions\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schemas schema version\" }\n ,\"microsoft.devices/iothubs\": { \"SingularDisplayName\": \"IoT hub\" }\n ,\"microsoft.devices/provisioningservices\": { \"SingularDisplayName\": \"Azure IoT Hub Device Provisioning Service (DPS)\" }\n ,\"microsoft.deviceupdate/accounts\": { \"SingularDisplayName\": \"Device Update for IoT Hub\" }\n ,\"microsoft.deviceupdate/updateaccounts\": { \"SingularDisplayName\": \"Device Update Account\" }\n ,\"microsoft.deviceupdate/updateaccounts/activedeployments\": { \"SingularDisplayName\": \"Device Update Active Deployment\" }\n ,\"microsoft.deviceupdate/updateaccounts/agents\": { \"SingularDisplayName\": \"Device Update Agent\" }\n ,\"microsoft.deviceupdate/updateaccounts/deployments\": { \"SingularDisplayName\": \"Device Update Deployment\" }\n ,\"microsoft.deviceupdate/updateaccounts/deviceclasses\": { \"SingularDisplayName\": \"Device Update Device Class\" }\n ,\"microsoft.deviceupdate/updateaccounts/updates\": { \"SingularDisplayName\": \"Device Update\" }\n ,\"microsoft.devops/pipelines\": { \"SingularDisplayName\": \"Microsoft.DevOps pipeline\" }\n ,\"microsoft.devopsinfrastructure/pools\": { \"SingularDisplayName\": \"Managed DevOps Pool\" }\n ,\"microsoft.devspaces/controllers\": { \"SingularDisplayName\": \"Microsoft.DevSpaces controller\" }\n ,\"microsoft.devtestlab/labs\": { \"SingularDisplayName\": \"DevTest lab\" }\n ,\"microsoft.devtestlab/labs/virtualmachines\": { \"SingularDisplayName\": \"DevTest Lab virtual machine\" }\n ,\"microsoft.devtestlab/schedules\": { \"SingularDisplayName\": \"Microsoft.DevTestLab schedule\" }\n ,\"microsoft.devtunnels/tunnelplans\": { \"SingularDisplayName\": \"Dev Tunnels Domain\" }\n ,\"microsoft.diagnostics/apollo\": { \"SingularDisplayName\": \"Microsoft.Diagnostics apollo\" }\n ,\"microsoft.digitaltwins/digitaltwinsinstances\": { \"SingularDisplayName\": \"Azure Digital Twins\" }\n ,\"microsoft.discovery/agents\": { \"SingularDisplayName\": \"Microsoft Discovery Agent\" }\n ,\"microsoft.discovery/bookshelves\": { \"SingularDisplayName\": \"Microsoft Discovery Bookshelf\" }\n ,\"microsoft.discovery/datacontainers\": { \"SingularDisplayName\": \"Microsoft Discovery Data Container\" }\n ,\"microsoft.discovery/datacontainers/dataassets\": { \"SingularDisplayName\": \"Data asset\" }\n ,\"microsoft.discovery/models\": { \"SingularDisplayName\": \"Microsoft Discovery Model\" }\n ,\"microsoft.discovery/storages\": { \"SingularDisplayName\": \"Microsoft Discovery Storage\" }\n ,\"microsoft.discovery/supercomputers\": { \"SingularDisplayName\": \"Microsoft Discovery Supercomputer\" }\n ,\"microsoft.discovery/supercomputers/nodepools\": { \"SingularDisplayName\": \"Nodepool\" }\n ,\"microsoft.discovery/tools\": { \"SingularDisplayName\": \"Microsoft Discovery Tool\" }\n ,\"microsoft.discovery/workflows\": { \"SingularDisplayName\": \"Microsoft Discovery Workflow\" }\n ,\"microsoft.discovery/workspaces\": { \"SingularDisplayName\": \"Microsoft Discovery Workspace\" }\n ,\"microsoft.discovery/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft Discovery Project\" }\n ,\"microsoft.documentdb/cassandraclusters\": { \"SingularDisplayName\": \"Azure Managed Instance for Apache Cassandra\" }\n ,\"microsoft.documentdb/databaseaccounts\": { \"SingularDisplayName\": \"Cosmos DB account\" }\n ,\"microsoft.documentdb/fleets\": { \"SingularDisplayName\": \"Azure Cosmos DB Fleet\" }\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccounts\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccountswithlocations\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\n ,\"microsoft.documentdb/mongoclusters\": { \"SingularDisplayName\": \"Azure Cosmos DB for MongoDB (vCore)\" }\n ,\"microsoft.documentdb/throughputpools\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pool\" }\n ,\"microsoft.documentdb/throughputpools/throughputpoolaccounts\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pools throughput pool account\" }\n ,\"microsoft.domainregistration/domains\": { \"SingularDisplayName\": \"App Service Domain\" }\n ,\"microsoft.domainregistration/topleveldomains\": { \"SingularDisplayName\": \"Microsoft.DomainRegistration top level domain\" }\n ,\"microsoft.durabletask/namespaces\": { \"SingularDisplayName\": \"Microsoft.DurableTask namespace\" }\n ,\"microsoft.durabletask/namespaces/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\n ,\"microsoft.durabletask/schedulers\": { \"SingularDisplayName\": \"Durable Task Scheduler\" }\n ,\"microsoft.durabletask/schedulers/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\n ,\"microsoft.dynamics365fraudprotection/instances\": { \"SingularDisplayName\": \"Microsoft.Dynamics365FraudProtection instance\" }\n ,\"microsoft.easm/workspaces\": { \"SingularDisplayName\": \"Microsoft Defender EASM\" }\n ,\"microsoft.edge/configurations\": { \"SingularDisplayName\": \"Site configuration\" }\n ,\"microsoft.edge/configurations/arcgatewayconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations arc gateway configuration\" }\n ,\"microsoft.edge/configurations/connectivityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations connectivity configuration\" }\n ,\"microsoft.edge/configurations/dynamicconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configuration\" }\n ,\"microsoft.edge/configurations/dynamicconfigurations/versions\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configurations version\" }\n ,\"microsoft.edge/configurations/networkconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations network configuration\" }\n ,\"microsoft.edge/configurations/securityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations security configuration\" }\n ,\"microsoft.edge/configurations/timeserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations time server configuration\" }\n ,\"microsoft.edge/connectivitystatuses\": { \"SingularDisplayName\": \"Microsoft.Edge connectivity statuse\" }\n ,\"microsoft.edge/disconnectedoperations\": { \"SingularDisplayName\": \"Azure Local - disconnected operations\" }\n ,\"microsoft.edge/siteawareresourcetypes\": { \"SingularDisplayName\": \"Microsoft.Edge site aware resource type\" }\n ,\"microsoft.edge/sites\": { \"SingularDisplayName\": \"Site manager - Azure Arc\" }\n ,\"microsoft.edge/updates\": { \"SingularDisplayName\": \"Microsoft.Edge update\" }\n ,\"microsoft.edgemarketplace/offers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace offer\" }\n ,\"microsoft.edgemarketplace/publishers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace publisher\" }\n ,\"microsoft.edgeorder/addresses\": { \"SingularDisplayName\": \"Azure Edge Hardware Center Address\" }\n ,\"microsoft.edgeorder/bootstrapconfigurations\": { \"SingularDisplayName\": \"Site Key\" }\n ,\"microsoft.edgeorder/orderitems\": { \"SingularDisplayName\": \"Azure Edge Hardware Center\" }\n ,\"microsoft.edgeorder/virtual_orderitems\": { \"SingularDisplayName\": \"Device\" }\n ,\"microsoft.edgezones/extendedzones\": { \"SingularDisplayName\": \"Microsoft.EdgeZones extended zone\" }\n ,\"microsoft.education/grants\": { \"SingularDisplayName\": \"Microsoft.Education grant\" }\n ,\"microsoft.education/labs\": { \"SingularDisplayName\": \"Microsoft.Education lab\" }\n ,\"microsoft.education/labs/joinrequests\": { \"SingularDisplayName\": \"Microsoft.Education labs join request\" }\n ,\"microsoft.education/labs/students\": { \"SingularDisplayName\": \"Microsoft.Education labs student\" }\n ,\"microsoft.education/studentlabs\": { \"SingularDisplayName\": \"Microsoft.Education student lab\" }\n ,\"microsoft.elastic/monitors\": { \"SingularDisplayName\": \"Elastic Cloud Resource\" }\n ,\"microsoft.elasticsan/elasticsans\": { \"SingularDisplayName\": \"Elastic SAN\" }\n ,\"microsoft.energydataplatform/energyservices\": { \"SingularDisplayName\": \"Microsoft.EnergyDataPlatform energy service\" }\n ,\"microsoft.enterpriseknowledgegraph/services\": { \"SingularDisplayName\": \"Microsoft.EnterpriseKnowledgeGraph service\" }\n ,\"microsoft.enterprisesupport/enterprisesupports\": { \"SingularDisplayName\": \"Microsoft.EnterpriseSupport enterprise support\" }\n ,\"microsoft.eventgrid/domains\": { \"SingularDisplayName\": \"Event Grid Domain\" }\n ,\"microsoft.eventgrid/domains/topics\": { \"SingularDisplayName\": \"Event Grid Domain Topic\" }\n ,\"microsoft.eventgrid/eventsubscriptions\": { \"SingularDisplayName\": \"Microsoft.EventGrid event subscription\" }\n ,\"microsoft.eventgrid/extensiontopics\": { \"SingularDisplayName\": \"Event Grid extension topic\" }\n ,\"microsoft.eventgrid/namespaces\": { \"SingularDisplayName\": \"Event Grid Namespace\" }\n ,\"microsoft.eventgrid/namespaces/topics\": { \"SingularDisplayName\": \"Event Grid Namespace Topic\" }\n ,\"microsoft.eventgrid/namespaces/topics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Subscription\" }\n ,\"microsoft.eventgrid/namespaces/topicspaces\": { \"SingularDisplayName\": \"Event Grid Topic Space\" }\n ,\"microsoft.eventgrid/partnerconfigurations\": { \"SingularDisplayName\": \"Event Grid Partner Configuration\" }\n ,\"microsoft.eventgrid/partnerdestinations\": { \"SingularDisplayName\": \"Event Grid Partner Destination\" }\n ,\"microsoft.eventgrid/partnernamespaces\": { \"SingularDisplayName\": \"Event Grid Partner Namespace\" }\n ,\"microsoft.eventgrid/partnernamespaces/channels\": { \"SingularDisplayName\": \"Event Grid Channel\" }\n ,\"microsoft.eventgrid/partnerregistrations\": { \"SingularDisplayName\": \"Event Grid Partner Registration\" }\n ,\"microsoft.eventgrid/partnertopics\": { \"SingularDisplayName\": \"Event Grid Partner Topic\" }\n ,\"microsoft.eventgrid/systemtopics\": { \"SingularDisplayName\": \"Event Grid System Topic\" }\n ,\"microsoft.eventgrid/systemtopics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Grid Subscriptions\" }\n ,\"microsoft.eventgrid/topics\": { \"SingularDisplayName\": \"Event Grid Topic\" }\n ,\"microsoft.eventgrid/topictypes\": { \"SingularDisplayName\": \"Microsoft.EventGrid topic type\" }\n ,\"microsoft.eventgrid/verifiedpartners\": { \"SingularDisplayName\": \"Microsoft.EventGrid verified partner\" }\n ,\"microsoft.eventhub/clusters\": { \"SingularDisplayName\": \"Event Hubs Cluster\" }\n ,\"microsoft.eventhub/namespaces\": { \"SingularDisplayName\": \"Event Hubs namespace\" }\n ,\"microsoft.eventhub/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Event Hubs Geo-DR Alias\" }\n ,\"microsoft.eventhub/namespaces/eventhubs\": { \"SingularDisplayName\": \"Event Hubs Instance\" }\n ,\"microsoft.eventhub/namespaces/providers/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\n ,\"microsoft.eventhub/namespaces/schemagroups\": { \"SingularDisplayName\": \"Schema Group\" }\n ,\"microsoft.experimentation/experimentworkspaces\": { \"SingularDisplayName\": \"Experiment Workspace\" }\n ,\"microsoft.extendedlocation/customlocations\": { \"SingularDisplayName\": \"Custom location\" }\n ,\"microsoft.fabric/capacities\": { \"SingularDisplayName\": \"Fabric Capacity\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/operationresults\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric operation result\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private endpoint connection\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private link resource\" }\n ,\"microsoft.fairfieldgardens/deviceprovisioningstates\": { \"SingularDisplayName\": \"Microsoft.FairfieldGardens device provisioning state\" }\n ,\"microsoft.fairfieldgardens/provisioningresources\": { \"SingularDisplayName\": \"Fairfield Gardens\" }\n ,\"microsoft.fairfieldgardens/provisioningresources/provisioningpolicies\": { \"SingularDisplayName\": \"Provisioning policy\" }\n ,\"microsoft.falcon/namespaces\": { \"SingularDisplayName\": \"Microsoft.Falcon namespace\" }\n ,\"microsoft.features/featureprovidernamespaces/featureconfigurations\": { \"SingularDisplayName\": \"Preview features\" }\n ,\"microsoft.fidalgo/devcenters\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenter\" }\n ,\"microsoft.fidalgo/devcenters/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters attachednetwork\" }\n ,\"microsoft.fidalgo/devcenters/catalogs\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalog\" }\n ,\"microsoft.fidalgo/devcenters/catalogs/items\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalogs item\" }\n ,\"microsoft.fidalgo/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters devboxdefinition\" }\n ,\"microsoft.fidalgo/devcenters/environmenttypes\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters environment type\" }\n ,\"microsoft.fidalgo/devcenters/galleries\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters gallery\" }\n ,\"microsoft.fidalgo/devcenters/galleries/images\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries image\" }\n ,\"microsoft.fidalgo/devcenters/galleries/images/versions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries images version\" }\n ,\"microsoft.fidalgo/devcenters/mappings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters mapping\" }\n ,\"microsoft.fidalgo/machinedefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo machinedefinition\" }\n ,\"microsoft.fidalgo/networksettings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksetting\" }\n ,\"microsoft.fidalgo/networksettings/healthchecks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksettings healthcheck\" }\n ,\"microsoft.fidalgo/projects\": { \"SingularDisplayName\": \"Microsoft.Fidalgo project\" }\n ,\"microsoft.fidalgo/projects/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects attachednetwork\" }\n ,\"microsoft.fidalgo/projects/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects devboxdefinition\" }\n ,\"microsoft.fidalgo/projects/environments\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects environment\" }\n ,\"microsoft.fidalgo/projects/pools\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects pool\" }\n ,\"microsoft.fileshares/fileshares\": { \"SingularDisplayName\": \"File share\" }\n ,\"microsoft.fluidrelay/fluidrelayservers\": { \"SingularDisplayName\": \"Fluid Relay\" }\n ,\"microsoft.footprintmonitoring/profiles\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profile\" }\n ,\"microsoft.footprintmonitoring/profiles/experiments\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles experiment\" }\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoint\" }\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints/conditions\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoints condition\" }\n ,\"microsoft.gallery/myareas/galleryitems\": { \"SingularDisplayName\": \"Template\" }\n ,\"microsoft.genomics/accounts\": { \"SingularDisplayName\": \"Genomics account\" }\n ,\"microsoft.graph/azureadapplication\": { \"SingularDisplayName\": \"Entra application\" }\n ,\"microsoft.graph/azureadapplicationprototype\": { \"SingularDisplayName\": \"Microsoft.Graph Azure ad application prototype\" }\n ,\"microsoft.graphservices/accounts\": { \"SingularDisplayName\": \"Metered API account\" }\n ,\"microsoft.guestconfiguration/guestconfigurationassignments\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignment\" }\n ,\"microsoft.guestconfiguration/guestconfigurationassignments/reports\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignments report\" }\n ,\"microsoft.hanaonazure/hanainstances\": { \"SingularDisplayName\": \"SAP HANA on Azure\" }\n ,\"microsoft.hanaonazure/sapmonitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP Solutions (classic)\" }\n ,\"microsoft.hardware/orders\": { \"SingularDisplayName\": \"Microsoft.Hardware order\" }\n ,\"microsoft.hardwaresecuritymodules/cloudhsmclusters\": { \"SingularDisplayName\": \"Azure Cloud HSM\" }\n ,\"microsoft.hdinsight/clusterpools\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster pool\" }\n ,\"microsoft.hdinsight/clusterpools/clusters\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster\" }\n ,\"microsoft.hdinsight/clusterpools/clusters/instanceviews\": { \"SingularDisplayName\": \"Microsoft.HDInsight clusterpools clusters instance view\" }\n ,\"microsoft.hdinsight/clusters\": { \"SingularDisplayName\": \"HDInsight cluster\" }\n ,\"microsoft.healthbot/healthbots\": { \"SingularDisplayName\": \"Healthcare agent service\" }\n ,\"microsoft.healthcareapis/services\": { \"SingularDisplayName\": \"Azure API for FHIR\" }\n ,\"microsoft.healthcareapis/workspaces\": { \"SingularDisplayName\": \"Health Data Services workspace\" }\n ,\"microsoft.healthcareapis/workspaces/dicomservices\": { \"SingularDisplayName\": \"DICOM service\" }\n ,\"microsoft.healthcareapis/workspaces/fhirservices\": { \"SingularDisplayName\": \"FHIR service\" }\n ,\"microsoft.healthcareapis/workspaces/iotconnectors\": { \"SingularDisplayName\": \"MedTech service\" }\n ,\"microsoft.healthdataaiservices/deidservices\": { \"SingularDisplayName\": \"De-identification Service\" }\n ,\"microsoft.healthmodel/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\n ,\"microsoft.healthplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.HealthPlatform account\" }\n ,\"microsoft.help/diagnostics\": { \"SingularDisplayName\": \"Microsoft.Help diagnostic\" }\n ,\"microsoft.help/selfhelp\": { \"SingularDisplayName\": \"Microsoft.Help self help\" }\n ,\"microsoft.help/simplifiedsolutions\": { \"SingularDisplayName\": \"Microsoft.Help simplified solution\" }\n ,\"microsoft.help/solutions\": { \"SingularDisplayName\": \"Microsoft.Help solution\" }\n ,\"microsoft.help/troubleshooters\": { \"SingularDisplayName\": \"Microsoft.Help troubleshooter\" }\n ,\"microsoft.hpcworkbench/instances\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instance\" }\n ,\"microsoft.hpcworkbench/instances/chambers\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chamber\" }\n ,\"microsoft.hpcworkbench/instances/chambers/accessprofiles\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers access profile\" }\n ,\"microsoft.hpcworkbench/instances/chambers/filerequests\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file request\" }\n ,\"microsoft.hpcworkbench/instances/chambers/files\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file\" }\n ,\"microsoft.hpcworkbench/instances/chambers/storages\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers storage\" }\n ,\"microsoft.hpcworkbench/instances/chambers/workloads\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers workload\" }\n ,\"microsoft.hpcworkbench/instances/consortiums\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances consortium\" }\n ,\"microsoft.hybridcloud/cloudconnections\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connection\" }\n ,\"microsoft.hybridcloud/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connector\" }\n ,\"microsoft.hybridcompute/arcgatewayassociatedresources\": { \"SingularDisplayName\": \"Arc gateway associated resource\" }\n ,\"microsoft.hybridcompute/arcserverwithwac\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/gateways\": { \"SingularDisplayName\": \"Arc gateway\" }\n ,\"microsoft.hybridcompute/licenses\": { \"SingularDisplayName\": \"Extended Security Updates - Windows Server 2012/R2\" }\n ,\"microsoft.hybridcompute/machines\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machines/microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\n ,\"microsoft.hybridcompute/machines/microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\n ,\"microsoft.hybridcompute/machines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.hybridcompute/machinesesu\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinespaygo\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinessoftwareassurance\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinessovereign\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Arc Private Link Scope\" }\n ,\"microsoft.hybridcompute/settings\": { \"SingularDisplayName\": \"Microsoft.HybridCompute setting\" }\n ,\"microsoft.hybridconnectivity/endpoints\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoint\" }\n ,\"microsoft.hybridconnectivity/endpoints/serviceconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoints service configuration\" }\n ,\"microsoft.hybridconnectivity/publiccloudconnectors\": { \"SingularDisplayName\": \"Multicloud connector\" }\n ,\"microsoft.hybridconnectivity/solutionconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configuration\" }\n ,\"microsoft.hybridconnectivity/solutionconfigurations/inventory\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configurations inventory\" }\n ,\"microsoft.hybridconnectivity/solutiontypes\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution type\" }\n ,\"microsoft.hybridcontainerservice/kubernetesversions\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService kubernetes version\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instance\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/agentpools\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances agent pool\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances hybrid identity metadata\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances upgrade profile\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusters\": { \"SingularDisplayName\": \"Kubernetes hybrid - Azure Arc\" }\n ,\"microsoft.hybridcontainerservice/skus\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService SKU\" }\n ,\"microsoft.hybridcontainerservice/storagespaces\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService storage space\" }\n ,\"microsoft.hybridcontainerservice/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService virtual network\" }\n ,\"microsoft.hybriddata/datamanagers\": { \"SingularDisplayName\": \"Microsoft.HybridData data manager\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data service\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definition\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions/jobs\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definitions job\" }\n ,\"microsoft.hybriddata/datamanagers/datastores\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store\" }\n ,\"microsoft.hybriddata/datamanagers/datastoretypes\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store type\" }\n ,\"microsoft.hybriddata/datamanagers/publickeys\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers public key\" }\n ,\"microsoft.hybridnetwork/configurationgroupvalues\": { \"SingularDisplayName\": \"Configuration Group Value\" }\n ,\"microsoft.hybridnetwork/devices\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Device\" }\n ,\"microsoft.hybridnetwork/networkfunctions\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Network Function\" }\n ,\"microsoft.hybridnetwork/proxypublishers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publisher\" }\n ,\"microsoft.hybridnetwork/proxypublishers/artifactstores\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers artifact store\" }\n ,\"microsoft.hybridnetwork/proxypublishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers configuration group schema\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition group\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition groups network function definition version\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design group\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design groups network service design version\" }\n ,\"microsoft.hybridnetwork/publishers\": { \"SingularDisplayName\": \"Publisher\" }\n ,\"microsoft.hybridnetwork/publishers/artifactstores\": { \"SingularDisplayName\": \"Publisher Artifact Store\" }\n ,\"microsoft.hybridnetwork/publishers/artifactstores/artifactmanifests\": { \"SingularDisplayName\": \"Publisher Artifact Manifest\" }\n ,\"microsoft.hybridnetwork/publishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Configuration Group Schema\" }\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Network Function Definition\" }\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Network Function Definition Version\" }\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Network Service Design\" }\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Network Service Design Version\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management container\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rolloutsequences\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout sequence\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rollouttiers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout tier\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specification\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollout\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts/statuses\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollouts statuse\" }\n ,\"microsoft.hybridnetwork/sitenetworkservices\": { \"SingularDisplayName\": \"Site Network Service\" }\n ,\"microsoft.hybridnetwork/sites\": { \"SingularDisplayName\": \"Site\" }\n })[tolower(id)]\n}\n", + "$fxv#10": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Prices |=========================================================================================================\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_transform_v1_2 function\n.create-or-alter function\nwith (docstring='Transforms Prices_raw into FOCUS 1.2.', folder='Prices')\nPrices_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n let prices = materialize(\n Prices_raw\n | extend PricingCurrency = coalesce(Currency, CurrencyCode) // CurrencyCode last as a fallback only\n | extend x_SkuId = coalesce(SkuId, SkuID)\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\n | extend x_SkuTerm = isoMonths(Term)\n | project-rename\n SkuMeter = MeterName,\n x_BaseUnitPrice = BasePrice,\n x_EffectivePeriodEnd = EffectiveEndDate,\n x_EffectivePeriodStart = EffectiveStartDate,\n x_PricingUnitDescription = UnitOfMeasure,\n x_SkuIncludedQuantity = IncludedQuantity,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuMeterType = MeterType,\n x_SkuOfferId = OfferID,\n x_SkuPartNumber = PartNumber,\n x_SkuPriceType = PriceType,\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTier = TierMinimumUnits\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, real(null)) // UnitPrice for savings plan is not the on-demand unit price\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, real(null)) // MarketPrice for savings plan is not the list price\n | extend ChargeCategory = case(\n x_SkuPriceType == 'Consumption', 'Usage',\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\n ''\n )\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\n //\n // Get latest ingested row based on the unique ID\n | extend x_IngestionTime = ingestion_time()\n );\n //\n // Meters for reservations and savings plans to identify commitment eligibility\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\n //\n // Copy list/base/contracted prices from on-demand SKUs\n prices\n | where x_SkuPriceType == 'SavingsPlan'\n // If we use join, specify the shuffle key\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\n //\n // Set CommitmentDiscountCategory for reuse\n | extend CommitmentDiscountCategory = case(\n x_SkuPriceType == 'ReservedInstance', 'Usage',\n x_SkuPriceType == 'SavingsPlan', 'Spend',\n ''\n )\n //\n // Calculate commitment discount eligibility\n // TODO: Would a join be faster?\n // TODO: Check this to ensure it's correct\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\n //\n // TODO: Implement x_CommitmentDiscountNormalizedRatio\n | extend x_CommitmentDiscountNormalizedRatio = real(null)\n //\n // Add PricingUnit and x_PricingBlockSize\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\n | lookup kind=leftouter (PricingUnits) on x_PricingUnitDescription\n //\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, real(null)) // Savings plan prices are for the effective price, not the contracted price\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\n | project\n BillingAccountId = tolower(case(\n BillingProfileId startswith '/', BillingProfileId,\n BillingAccountId startswith '/', BillingAccountId,\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\n )),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType = case(\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\n ''\n ),\n CommitmentDiscountUnit = case(\n isempty(CommitmentDiscountCategory), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), PricingUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', PricingUnit),\n ''\n ),\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory = case(\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed',\n ''\n ),\n PricingCurrency,\n PricingUnit,\n SkuId = coalesce(ProductId, ProductID),\n SkuMeter,\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement = case(\n strlen(x_BillingAccountId) > 32, 'MCA',\n strlen(x_BillingAccountId) < 32, 'EA',\n 'Unknown'\n ),\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingSubcategory = case(\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\n ''\n ),\n x_PricingUnitDescription,\n x_SkuDescription = Product,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\n}\n\n// Prices_final_v1_2 table\n.create-merge table Prices_final_v1_2 (\n BillingAccountId: string,\n BillingAccountName: string,\n BillingCurrency: string,\n ChargeCategory: string,\n CommitmentDiscountCategory: string,\n CommitmentDiscountType: string,\n CommitmentDiscountUnit: string,\n ContractedUnitPrice: real,\n ListUnitPrice: real,\n PricingCategory: string,\n PricingCurrency: string, // Azure\n PricingUnit: string,\n SkuId: string,\n SkuMeter: string, // Azure\n SkuPriceId: string,\n SkuPriceIdv2: string, // Hubs add-on\n x_BaseUnitPrice: real, // Azure\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure MCA\n x_BillingProfileId: string, // Azure MCA\n x_CommitmentDiscountNormalizedRatio: real, // Hubs add-on\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_ContractedUnitPriceDiscount: real, // Hubs add-on\n x_ContractedUnitPriceDiscountPercent: real, // Hubs add-on\n x_EffectivePeriodEnd: datetime, // Azure\n x_EffectivePeriodStart: datetime, // Azure\n x_EffectiveUnitPrice: real, // Azure\n x_EffectiveUnitPriceDiscount: real, // Hubs add-on\n x_EffectiveUnitPriceDiscountPercent: real, // Hubs add-on\n x_IngestionTime: datetime, // Hubs add-on\n x_PricingBlockSize: real, // Hubs add-on\n x_PricingSubcategory: string, // Hubs add-on\n x_PricingUnitDescription: string, // Azure\n x_SkuDescription: string, // Azure\n x_SkuId: string, // Azure\n x_SkuIncludedQuantity: real, // Azure EA\n x_SkuMeterCategory: string, // Azure\n x_SkuMeterId: string, // Azure\n x_SkuMeterSubcategory: string, // Azure\n x_SkuMeterType: string, // Azure\n x_SkuPriceType: string, // Azure\n x_SkuProductId: string, // Azure\n x_SkuRegion: string, // Azure\n x_SkuServiceFamily: string, // Azure\n x_SkuOfferId: string, // Azure EA\n x_SkuPartNumber: string, // Azure EA\n x_SkuTerm: int, // Azure\n x_SkuTier: real, // Azure MCA\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_TotalUnitPriceDiscount: real, // Hubs add-on\n x_TotalUnitPriceDiscountPercent: real // Hubs add-on\n)\n\n// Update policy for Prices_raw -> Prices_final_v1_2\n.alter table Prices_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Prices_raw\",\n \"Query\": \"Prices_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Cost and usage |=================================================================================================\n// Supported versions:\n// - MS: 1.2-preview, 1.0, 1.0-preview(v1)\n// https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0\n// https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024\n// https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 \n// https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All costs transformed to FOCUS 1.2.', folder='Costs')\nCosts_transform_v1_2()\n{\n let checkString = (column: string, oldValue: string, newValue: string) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n let checkInt = (column: string, oldValue: int, newValue: int) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n let checkReal = (column: string, oldValue: real, newValue: real) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n Costs_raw\n //\n // Dedupe rows\n | extend x_IngestionTime = ingestion_time()\n | extend x_ChargeId = ''\n // TODO: Consider adding a unique charge ID per row\n // hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // // 1. Resource hierarchy (including resource name), highest to lowest\n // BillingAccountId,\n // x_InvoiceSectionId,\n // x_AccountOwnerId,\n // SubAccountId,\n // x_ResourceGroupName,\n // ResourceName,\n // // 2. Resource details\n // ResourceId,\n // RegionId,\n // Tags,\n // CommitmentDiscountId,\n // x_CostCenter,\n // // 4. Meter details\n // SkuPriceId,\n // x_SkuMeterId,\n // x_SkuPartNumber,\n // x_SkuOfferId,\n // x_SkuDetails,\n // // 5. Date\n // ChargePeriodStart\n // ))\n //\n // Identify data quality issues\n // TODO: Remove x_SourceChanges in v1_3 (or later)\n | extend x_SourceChanges = trim_end(',', strcat(\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\n 'XEffectiveUnitPriceRoundingError,', ''),\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\n ))\n //\n // Handle provider columns that moved to FOCUS\n | extend PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency)\n //\n // Backup original prices/costs before the merge\n | extend old_ContractedCost = ContractedCost\n | extend old_ContractedUnitPrice = ContractedUnitPrice\n | extend old_ListCost = ListCost\n | extend old_ListUnitPrice = ListUnitPrice\n | extend old_x_EffectiveUnitPrice = x_EffectiveUnitPrice\n //\n // Fix columns needed in other changes\n | extend old_ProviderName = ProviderName, ProviderName = case(\n isnotempty(ProviderName), ProviderName,\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\n ''\n )\n //\n // Identify source\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\n ''\n ))\n // Append version check error code\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\n )\n //\n // Fix quantities\n | extend old_PricingQuantity = PricingQuantity, PricingQuantity = case(\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\n PricingQuantity\n )\n | extend old_ConsumedQuantity = ConsumedQuantity, ConsumedQuantity = case(\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\n ConsumedQuantity\n )\n //\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\n and (isempty(ListUnitPrice) or isempty(ContractedUnitPrice) or ListUnitPrice == 0 or ContractedUnitPrice == 0)\n and x_EffectiveUnitPrice != 0\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\n | as allCosts\n | where tmp_MissingPrices\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | as costsWithMissingPrices\n | join kind=leftouter (\n Prices_final_v1_2\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\n ) on tmp_ReservationPriceLookupKey\n //\n // Select the best price to use for each row\n | extend x_EffectiveUnitPrice = case(\n // If price is a rounding error away from the billed price, use the billed price\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\n // If price is a rounding error away from the contracted price, use the contracted price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\n x_EffectiveUnitPrice\n )\n | extend ContractedUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\n x_EffectiveUnitPrice\n )\n | extend ListUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // Otherwise, assume the contracted price is the same as list price to support aggregations\n ContractedUnitPrice\n )\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\n | extend ContractedCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\n // ContractedCost is 0 in all other scenarios...\n // If 0 and there's a billed cost and prices are the same, use BilledCost\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume EffectiveCost\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\n // Fall back to the original value for any unhandled scenarios\n ContractedCost\n )\n | extend ListCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\n // ListCost is 0 in all other scenarios...\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume ContractedCost\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\n // Fall back to the original value for any unhandled scenarios\n ListCost\n )\n // Merge the rest of the unmodified cost records and remove excess columns\n | union (allCosts | where not(tmp_MissingPrices))\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\n //\n | extend SkuPriceDetails = parse_json(SkuPriceDetails)\n | extend Tags = parse_json(Tags)\n | extend x_SkuDetails = parse_json(x_SkuDetails)\n //\n // Handle FOCUS 1.0-preview\n | extend old_ChargeSubcategory = ChargeSubcategory\n | extend old_ChargeCategory = ChargeCategory, ChargeCategory = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Credit', 'Credit',\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\n ChargeCategory\n )\n | extend old_ChargeClass = ChargeClass, ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass)\n //\n // Populate CapacityReservationId when not specified\n | extend CapacityReservationId = coalesce(CapacityReservationId, tostring(coalesce(x_SkuDetails.VMCapacityReservationId, SkuPriceDetails.VMCapacityReservationId, SkuPriceDetails.x_VMCapacityReservationId)))\n | extend old_CapacityReservationStatus = CapacityReservationStatus, CapacityReservationStatus = case(\n isempty(CapacityReservationId), '',\n isnotempty(CapacityReservationStatus), CapacityReservationStatus,\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\n 'Used'\n )\n //\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\n | extend old_ChargeFrequency = ChargeFrequency, ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency)\n //\n // Commitment discounts\n | extend x_CommitmentDiscountNormalizedRatio = case(\n // Calculate from CommitmentDiscountQuantity, if specified\n isnotempty(CommitmentDiscountQuantity) and CommitmentDiscountQuantity != 0, CommitmentDiscountQuantity / PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\n // Not applicable\n isempty(CommitmentDiscountStatus), real(null),\n // Parse from SKU details if not specified explicitly\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, SkuPriceDetails.RINormalizationRatio, SkuPriceDetails.x_RINormalizationRatio, dynamic(1)))\n )\n | extend old_CommitmentDiscountQuantity = CommitmentDiscountQuantity, CommitmentDiscountQuantity = case(\n // FOCUS 1.2\n isnotempty(CommitmentDiscountQuantity), CommitmentDiscountQuantity,\n // FOCUS 1.0-preview, 1.0\n isempty(CommitmentDiscountStatus), real(null),\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\n real(null)\n )\n | extend old_CommitmentDiscountUnit = CommitmentDiscountUnit, CommitmentDiscountUnit = case(\n // FOCUS 1.2\n isnotempty(CommitmentDiscountUnit), CommitmentDiscountUnit,\n // FOCUS 1.0\n isempty(CommitmentDiscountQuantity), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\n ''\n )\n | extend old_CommitmentDiscountStatus = CommitmentDiscountStatus, CommitmentDiscountStatus = case(\n // FOCUS 1.0+\n isnotempty(CommitmentDiscountStatus), CommitmentDiscountStatus,\n // FOCUS 1.0-preview\n ChargeSubcategory == 'Used Commitment', 'Used',\n ChargeSubcategory == 'Unused Commitment', 'Unused',\n ''\n )\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n //\n // Pricing\n | extend old_x_AmortizationClass = x_AmortizationClass, x_AmortizationClass = case(\n // FOCUS 1.2\n isnotempty(x_AmortizationClass), x_AmortizationClass,\n // FOCUS 1.0-preview+\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\n ''\n )\n | extend old_PricingCategory = PricingCategory, PricingCategory = case(\n // FOCUS 1.0+\n isnotempty(PricingCategory), PricingCategory,\n // FOCUS 1.0-preview\n PricingCategory == 'On-Demand', 'Standard',\n PricingCategory == 'Commitment-Based', 'Committed',\n ''\n )\n //\n // Commitment discount utilization\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n //\n // BUG: Fix ContractedCost that has bad values\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\n //\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), real(null))\n | extend old_ConsumedUnit = ConsumedUnit, ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\n //\n // Convert IDs to lowercase for consistency\n | extend BillingAccountId = tolower(BillingAccountId)\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\n //\n // BUG: Remove EffectiveCost for commitment discount purchases\n | extend old_EffectiveCost = EffectiveCost, EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), EffectiveCost)\n | extend old_x_EffectiveCostInUsd = x_EffectiveCostInUsd, x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), x_EffectiveCostInUsd)\n //\n // Clean up resource columns\n | extend old_ResourceId = ResourceId, ResourceId = case(\n isnotempty(ResourceId), ResourceId,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\n ResourceId\n )\n | extend old_ResourceName = ResourceName, ResourceName = tolower(case(\n isnotempty(ResourceName), ResourceName,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\n ResourceName\n ))\n | extend old_x_ResourceType = x_ResourceType, x_ResourceType = case(\n isnotempty(x_ResourceType), x_ResourceType,\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\n x_ResourceType\n )\n | extend old_ResourceType = ResourceType, ResourceType = case(\n // Use existing resource type display name unless it's an internal resource type ID\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\n // Use CommitmentDiscountType for commitment discount purchases\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\n // Look up display name from internal type\n isnotempty(x_ResourceType), coalesce(tostring(resource_type(x_ResourceType).SingularDisplayName), ResourceType, x_ResourceType),\n ResourceType\n )\n //\n // Handle missing values\n | extend old_PublisherName = PublisherName, PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, '')\n //\n // Handle FOCUS 1.0-preview Region column\n | extend old_Region = Region\n | extend old_RegionId = RegionId, RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region))\n | extend RegionName = coalesce(RegionName, Region)\n //\n // SKU properties\n | extend x_SkuCoreCount = toint(coalesce(SkuPriceDetails.CoreCount, SkuPriceDetails.x_VCPUs, x_SkuDetails.VCPUs, SkuPriceDetails.x_VCores, x_SkuDetails.VCores, SkuPriceDetails.x_vCores, x_SkuDetails.vCores))\n | extend x_SkuInstanceType = tostring(coalesce(SkuPriceDetails.InstanceType, SkuPriceDetails.x_ServiceType, x_SkuDetails.ServiceType, SkuPriceDetails.x_ServerSku, x_SkuDetails.ServerSku))\n | extend x_SkuOperatingSystem = case(\n isnotempty(SkuPriceDetails.OperatingSystem), SkuPriceDetails.OperatingSystem,\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Canonical', 'Linux',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType)\n )\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\n | extend SkuPriceDetails = case(\n // FOCUS 1.2\n isnotempty(SkuPriceDetails), SkuPriceDetails,\n // FOCUS 1.0-preview, 1.0\n parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\n // Prefix all keys with x_ first to avoid double-prefixing\n , @'([\\{,])\"', @'\\1\"x_')\n // CoreCount for number of CPUs/vCPUs/cores/vCores\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\n // TODO: DiskSpace for disk size in GiB\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\n // TODO: GpuCount for the number of GPUs\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\n // TODO: InstanceSeries for the size family/series\n // TODO: MemorySize for the RAM in GiB\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\n // OperatingSystem for the OS name\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\n )\n )\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\n SkuPriceDetails)\n //\n // Azure Hybrid Benefit\n | extend tmp_SqlAhb = tolower(coalesce(x_SkuDetails.AHB, SkuPriceDetails.x_AHB))\n | extend x_SkuLicenseType = case(\n ChargeCategory != 'Usage', '',\n x_SkuMeterCategory in ('Virtual Machines', 'Virtual Machine Licenses') and (x_SkuMeterSubcategory contains 'Windows' or coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL'), 'Windows Server',\n isnotempty(tmp_SqlAhb) or x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\n ''\n )\n | extend x_SkuLicenseStatus = case(\n isempty(x_SkuLicenseType), '',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL' or tmp_SqlAhb == 'true' or x_SkuMeterSubcategory contains 'Azure Hybrid Benefit', 'Enabled',\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not Enabled',\n ''\n )\n | extend x_SkuLicenseQuantity = case(\n isempty(x_SkuCoreCount) or isempty(x_SkuLicenseType), int(null),\n x_SkuCoreCount <= 8, int(8),\n x_SkuCoreCount > 8, x_SkuCoreCount,\n int(null)\n )\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\n //\n // Savings\n | extend x_CommitmentDiscountSavings = iff(isempty(ContractedCost) or ContractedCost == 0 or ContractedCost - EffectiveCost < 0.0001, real(0), ContractedCost - EffectiveCost)\n | extend x_NegotiatedDiscountSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - ContractedCost < 0.0001, real(0), ListCost - ContractedCost)\n | extend x_TotalSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - EffectiveCost < 0.0001, real(0), ListCost - EffectiveCost)\n | extend x_CommitmentDiscountPercent = iff(isempty(ContractedUnitPrice) or ContractedUnitPrice == 0 or ContractedUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\n | extend x_NegotiatedDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - ContractedUnitPrice < 0.0001, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\n | extend x_TotalDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\n //\n // Minor fixes\n | extend old_BillingPeriodEnd = BillingPeriodEnd, BillingPeriodEnd = startofmonth(BillingPeriodEnd)\n | extend old_BillingPeriodStart = BillingPeriodStart, BillingPeriodStart = startofmonth(BillingPeriodStart)\n //\n // Sort columns and apply final transforms\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n CapacityReservationId,\n CapacityReservationStatus,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountQuantity,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\n EffectiveCost,\n InvoiceId = coalesce(InvoiceId, x_InvoiceId),\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory, // TODO: Populate ServiceSubcategory from ServiceName when missing\n SkuId,\n SkuMeter,\n SkuPriceDetails,\n SkuPriceId,\n SubAccountId,\n SubAccountName = iff(isempty(SubAccountId), '', SubAccountName),\n SubAccountType,\n Tags,\n x_AccountId = iff(x_AccountId == '-2', '', x_AccountId),\n x_AccountName = iff(x_AccountId == '-2', '', x_AccountName),\n x_AccountOwnerId = iff(x_AccountId == '-2', '', x_AccountOwnerId),\n x_AmortizationClass,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement = case(\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\n ProviderName\n ),\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingItemCode,\n x_BillingItemName,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountPercent,\n x_CommitmentDiscountSavings,\n x_CommitmentDiscountSpendEligibility = '', // TODO: Add x_CommitmentDiscountSpendEligibility for Costs\n x_CommitmentDiscountUsageEligibility = '', // TODO: Add x_CommitmentDiscountUsageEligibility for Costs\n x_CommitmentDiscountUtilizationAmount,\n x_CommitmentDiscountUtilizationPotential,\n x_CommodityCode,\n x_CommodityName,\n x_ComponentName,\n x_ComponentType,\n x_ConsumedCoreHours,\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd),\n x_CostAllocationRuleName,\n x_CostCategories = parse_json(x_CostCategories),\n x_CostCenter,\n x_CostType,\n x_Credits = parse_json(x_Credits),\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount = parse_json(x_Discount),\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InstanceID,\n x_InvoiceIssuerId,\n x_InvoiceSectionId = case(\n x_InvoiceSectionId == '-2', '',\n x_InvoiceSectionId\n ),\n x_InvoiceSectionName = case(\n x_InvoiceSectionName == 'Unassigned', '',\n x_InvoiceSectionName\n ),\n x_ListCostInUsd,\n x_Location,\n x_NegotiatedDiscountPercent,\n x_NegotiatedDiscountSavings,\n x_Operation,\n x_OwnerAccountID,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription = iff(x_PricingUnitDescription == 'Unassigned', '', x_PricingUnitDescription),\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName = tolower(x_ResourceGroupName),\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServiceModel, // TODO: Populate from ServiceName when missing\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuCoreCount,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuInstanceType,\n x_SkuIsCreditEligible,\n x_SkuLicenseQuantity,\n x_SkuLicenseStatus,\n x_SkuLicenseType,\n x_SkuLicenseUnit,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOperatingSystem,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuPlanName,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceValues = bag_merge(\n checkString('BillingPeriodEnd', old_BillingPeriodEnd, BillingPeriodEnd),\n checkString('BillingPeriodStart', old_BillingPeriodStart, BillingPeriodStart),\n checkString('CapacityReservationStatus', old_CapacityReservationStatus, CapacityReservationStatus),\n checkString('ChargeCategory', old_ChargeCategory, ChargeCategory),\n checkString('ChargeClass', old_ChargeClass, ChargeClass),\n checkString('ChargeSubcategory', old_ChargeSubcategory, ''), // Not included in final schema; use empty string\n checkString('ChargeFrequency', old_ChargeFrequency, ChargeFrequency),\n checkReal('CommitmentDiscountQuantity', old_CommitmentDiscountQuantity, CommitmentDiscountQuantity),\n checkString('CommitmentDiscountUnit', old_CommitmentDiscountUnit, CommitmentDiscountUnit),\n checkString('CommitmentDiscountStatus', old_CommitmentDiscountStatus, CommitmentDiscountStatus),\n checkReal('ConsumedQuantity', old_ConsumedQuantity, ConsumedQuantity),\n checkString('ConsumedUnit', old_ConsumedUnit, ConsumedUnit),\n checkReal('ContractedCost', old_ContractedCost, ContractedCost),\n checkReal('ContractedUnitPrice', old_ContractedUnitPrice, ContractedUnitPrice),\n checkReal('EffectiveCost', old_EffectiveCost, EffectiveCost),\n checkReal('ListCost', old_ListCost, ListCost),\n checkReal('ListUnitPrice', old_ListUnitPrice, ListUnitPrice),\n checkString('PricingCategory', old_PricingCategory, PricingCategory),\n checkReal('PricingQuantity', old_PricingQuantity, PricingQuantity),\n checkString('ProviderName', old_ProviderName, ProviderName),\n checkString('PublisherName', old_PublisherName, PublisherName),\n checkString('Region', old_Region, ''), // Not included in final schema; use empty string\n checkString('RegionId', old_RegionId, RegionId),\n checkString('ResourceId', old_ResourceId, ResourceId),\n checkString('ResourceName', old_ResourceName, ResourceName),\n checkString('ResourceType', old_ResourceType, ResourceType),\n checkString('x_AmortizationClass', old_x_AmortizationClass, x_AmortizationClass),\n checkReal('x_EffectiveCostInUsd', old_x_EffectiveCostInUsd, x_EffectiveCostInUsd),\n checkReal('x_EffectiveUnitPrice', old_x_EffectiveUnitPrice, x_EffectiveUnitPrice),\n checkString('x_ResourceType', old_x_ResourceType, x_ResourceType)\n ),\n x_SourceVersion,\n x_SubproductName,\n x_TotalDiscountPercent,\n x_TotalSavings,\n x_UsageType\n}\n\n// Costs_final_v1_2 table\n.create-merge table Costs_final_v1_2 (\n AvailabilityZone: string,\n BilledCost: real,\n BillingAccountId: string,\n BillingAccountName: string,\n BillingAccountType: string,\n BillingCurrency: string,\n BillingPeriodEnd: datetime,\n BillingPeriodStart: datetime,\n CapacityReservationId: string,\n CapacityReservationStatus: string,\n ChargeCategory: string,\n ChargeClass: string,\n ChargeDescription: string,\n ChargeFrequency: string,\n ChargePeriodEnd: datetime,\n ChargePeriodStart: datetime,\n CommitmentDiscountCategory: string,\n CommitmentDiscountId: string,\n CommitmentDiscountName: string,\n CommitmentDiscountQuantity: real,\n CommitmentDiscountStatus: string,\n CommitmentDiscountType: string,\n CommitmentDiscountUnit: string,\n ConsumedQuantity: real,\n ConsumedUnit: string,\n ContractedCost: real,\n ContractedUnitPrice: real,\n EffectiveCost: real,\n InvoiceId: string,\n InvoiceIssuerName: string,\n ListCost: real,\n ListUnitPrice: real,\n PricingCategory: string,\n PricingCurrency: string,\n PricingQuantity: real,\n PricingUnit: string,\n ProviderName: string,\n PublisherName: string,\n RegionId: string,\n RegionName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n ServiceCategory: string,\n ServiceName: string,\n ServiceSubcategory: string,\n SkuId: string,\n SkuMeter: string,\n SkuPriceDetails: dynamic,\n SkuPriceId: string,\n SubAccountId: string,\n SubAccountName: string,\n SubAccountType: string,\n Tags: dynamic,\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_AmortizationClass: string, // Azure 1.2-preview+\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingItemCode: string, // Alibaba 1.0\n x_BillingItemName: string, // Alibaba 1.0\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_CommitmentDiscountNormalizedRatio: real, // Azure 1.2-preview+\n x_CommitmentDiscountPercent: real, // Hubs add-on\n x_CommitmentDiscountSavings: real, // Hubs add-on\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_CommitmentDiscountUtilizationAmount: real, // Hubs add-on\n x_CommitmentDiscountUtilizationPotential: real, // Hubs add-on\n x_CommodityCode: string, // Alibaba 1.0\n x_CommodityName: string, // Alibaba 1.0\n x_ComponentName: string, // Tencent 1.0\n x_ComponentType: string, // Tencent 1.0\n x_ConsumedCoreHours: real, // Hubs add-on\n x_ContractedCostInUsd: real, // Azure 1.0+\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_CostType: string, // GCP Jan 2024\n x_Credits: dynamic, // GCP Jan 2024\n x_CurrencyConversionRate: real, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: dynamic, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0\n x_IngestionTime: datetime, // Hubs add-on\n x_InstanceID: string, // Alibaba 1.0\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_NegotiatedDiscountPercent:real, // Hubs add-on\n x_NegotiatedDiscountSavings:real, // Hubs add-on\n x_Operation: string, // AWS 1.0\n x_OwnerAccountID: string, // Tencent 1.0\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServiceModel: string, // Azure 1.2-preview+\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuCoreCount: int, // Hubs add-on\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\n x_SkuInstanceType: string, // Hubs add-on\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuLicenseQuantity: int, // Hubs add-on\n x_SkuLicenseStatus: string, // Hubs add-on\n x_SkuLicenseType: string, // Hubs add-on\n x_SkuLicenseUnit: string, // Hubs add-on\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOperatingSystem: string, // Hubs add-on\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuPlanName: string, // Azure 1.2-preview+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceChanges: string, // Hubs add-on\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceValues: dynamic, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubproductName: string, // Tencent 1.0\n x_TotalDiscountPercent: real, // Hubs add-on\n x_TotalSavings: real, // Hubs add-on\n x_UsageType: string // AWS 1.0\n)\n\n// Update policy for Costs_raw -> Costs_final_v1_2 table\n.alter table Costs_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Costs_raw\",\n \"Query\": \"Costs_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Actual costs |===================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_transform_v1_2 function\n.create-or-alter function\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\nActualCosts_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n ActualCosts_raw\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodEnd = Date + 1d,\n ChargePeriodStart = Date,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId = '',\n SkuMeter = MeterName,\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentType = '',\n x_ComponentName = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = '',\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel,\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for ActualCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": true,\n \"Source\": \"ActualCosts_raw\",\n \"Query\": \"ActualCosts_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Amortized costs |================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_transform_v1_2 function\n.create-or-alter function\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\nAmortizedCosts_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n AmortizedCosts_raw\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodEnd = Date + 1d,\n ChargePeriodStart = Date,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId = '',\n SkuMeter = MeterName,\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentType = '',\n x_ComponentName = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = '',\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel,\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for AmortizedCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": true,\n \"Source\": \"AmortizedCosts_raw\",\n \"Query\": \"AmortizedCosts_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All commitment discount usage transformed to FOCUS 1.2. This includes reservationdeatils_raw.', folder='Commitment discounts')\nCommitmentDiscountUsage_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n CommitmentDiscountUsage_raw\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Handle resource columns\n | extend ResourceId = tolower(InstanceId)\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n //\n // Sort columns and apply final transforms\n | project\n ChargePeriodEnd = UsageDate + 1d,\n ChargePeriodStart = UsageDate,\n CommitmentDiscountCategory = 'Usage',\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\n CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\n CommitmentDiscountType = 'Reservation',\n CommitmentDiscountUnit = case(\n InstanceFlexibilityRatio == 1, 'Hours',\n InstanceFlexibilityRatio != 1, 'Normalized Hours',\n ''\n ),\n ConsumedQuantity = UsedHours,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SubAccountId,\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\n x_CommitmentDiscountCommittedAmount = ReservedHours,\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\n x_IngestionTime = ingestion_time(),\n x_ResourceGroupName,\n x_ResourceType,\n // x_RowId = hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // CommitmentDiscountId,\n // ResourceId,\n // ChargePeriodStart\n // )),\n x_ServiceModel,\n x_SkuOrderId = ReservationOrderId,\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\n}\n\n// CommitmentDiscountUsage_final_v1_2 table\n.create-merge table CommitmentDiscountUsage_final_v1_2 (\n ChargePeriodEnd: datetime, // Hubs add-on\n ChargePeriodStart: datetime, // MS 2023-03-01\n CommitmentDiscountCategory: string, // Hubs add-on\n CommitmentDiscountId: string, // MS 2023-03-01\n CommitmentDiscountQuantity: real, // MS 2023-03-01\n CommitmentDiscountType: string, // Hubs add-on\n CommitmentDiscountUnit: string, // Hubs add-on\n ConsumedQuantity: real, // MS 2023-03-01\n ProviderName: string, // Hubs add-on\n ResourceId: string, // MS 2023-03-01\n ResourceName: string, // Hubs add-on\n ResourceType: string, // Hubs add-on\n ServiceCategory: string, // Hubs add-on\n ServiceName: string, // Hubs add-on\n ServiceSubcategory: string, // Hubs add-on\n SubAccountId: string, // Hubs add-on\n x_CommitmentDiscountCommittedCount: real, // MS 2023-03-01\n x_CommitmentDiscountCommittedAmount: real, // MS 2023-03-01\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\n x_CommitmentDiscountNormalizedRatio: real, // MS 2023-03-01\n x_IngestionTime: datetime, // Hubs add-on\n x_ResourceGroupName: string, // Hubs add-on\n x_ResourceType: string, // Hubs add-on\n x_ServiceModel: string, // Hubs add-on\n x_SkuOrderId: string, // MS 2023-03-01\n x_SkuSize: string, // MS 2023-03-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string // Hubs add-on\n)\n\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_2 table\n.alter table CommitmentDiscountUsage_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"CommitmentDiscountUsage_raw\",\n \"Query\": \"CommitmentDiscountUsage_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All recommendations transformed to FOCUS 1.2.', folder='Recommendations')\nRecommendations_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Recommendations_raw\n | extend x_IngestionTime = ingestion_time()\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Convert JSON cost columns to real\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\n //\n // Build recommendation details\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\n | extend x_RecommendationDetails = case(\n // Use incoming x_RecommendationDetails first\n isnotempty(x_RecommendationDetails), x_RecommendationDetails,\n // Create one for reservation recommendations if needed\n x_SourceType == 'ReservationRecommendations', bag_pack(\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\n 'CommitmentDiscountResourceType', ResourceType,\n 'CommitmentDiscountScope', Scope,\n 'LookbackPeriodDuration', case(\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\n ''\n ),\n 'LookbackPeriodStart', FirstUsageDate,\n 'RecommendedQuantity', RecommendedQuantity,\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\n 'RegionId', Location,\n 'RegionName', RegionName,\n 'SkuMeterId', MeterId,\n 'SkuPriceDetails', SkuProperties,\n 'SkuSize', coalesce(SKU, SkuName),\n 'SkuTerm', isoMonths(Term)\n ),\n dynamic({})\n )\n //\n // Prefer specified date, then fall back to generating a date based on reservation recommendation lookback period, then validate to ensure it's not in the future\n | extend x_RecommendationDate = coalesce(x_RecommendationDate, FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d))\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\n //\n | project\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n SubAccountId = coalesce(SubAccountId, iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), '')),\n SubAccountName,\n x_EffectiveCostAfter = coalesce(x_EffectiveCostAfter, TotalCostWithReservedInstances),\n x_EffectiveCostBefore = coalesce(x_EffectiveCostBefore, CostWithNoReservedInstances),\n x_EffectiveCostSavings = coalesce(x_EffectiveCostSavings, NetSavings),\n x_IngestionTime,\n x_RecommendationCategory, // TODO: Set for reservation recommendations\n x_RecommendationDate,\n x_RecommendationDescription,\n x_RecommendationDetails,\n x_RecommendationId, // TODO: Set for reservation recommendations\n x_ResourceGroupName,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n// Recommendations_final_v1_2 table\n.create-merge table Recommendations_final_v1_2 (\n ProviderName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n SubAccountId: string,\n SubAccountName: string,\n x_EffectiveCostAfter: real,\n x_EffectiveCostBefore: real,\n x_EffectiveCostSavings: real,\n x_IngestionTime: datetime,\n x_RecommendationCategory: string,\n x_RecommendationDate: datetime,\n x_RecommendationDescription: string,\n x_RecommendationDetails: dynamic,\n x_RecommendationId: string,\n x_ResourceGroupName: string,\n x_SourceName: string,\n x_SourceProvider: string,\n x_SourceType: string,\n x_SourceVersion: string\n)\n\n// Update policy for Recommendations_raw -> Recommendations_final_v1_2 table\n.alter table Recommendations_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Recommendations_raw\",\n \"Query\": \"Recommendations_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All transactions transformed to FOCUS 1.2.', folder='Transactions')\nTransactions_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Transactions_raw\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Handle BillingPeriodStart/End\n | extend BillingMonth = tostring(BillingMonth)\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\n //\n // Sort columns and apply final transforms\n | project\n BilledCost = Amount,\n BillingAccountId = case(\n BillingProfileId startswith '/', BillingProfileId,\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\n ''\n ),\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\n BillingCurrency = Currency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory = case(\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = case(\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\n EventType == 'Refund', 'Correction',\n ''\n ),\n ChargeDescription = Description,\n ChargeFrequency = case(\n BillingFrequency == 'OneTime', 'One-Time',\n BillingFrequency == 'Recurring', 'Recurring',\n BillingFrequency\n ),\n ChargePeriodStart = EventDate,\n InvoiceId,\n PricingQuantity = Quantity,\n PricingUnit = 'Reservations',\n ProviderName,\n RegionId = Region,\n RegionName = Region,\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerEmail,\n x_CostCenter = CostCenter,\n x_InvoiceNumber = Invoice,\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\n x_IngestionTime = ingestion_time(),\n x_MonetaryCommitment = MonetaryCommitment,\n x_Overage = Overage,\n x_PurchasingBillingAccountId = PurchasingEnrollment,\n x_SkuOrderId = ReservationOrderId,\n x_SkuOrderName = ReservationOrderName,\n x_SkuSize = ArmSkuName,\n x_SkuTerm = isoMonths(Term),\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId = PurchasingSubscriptionGuid,\n x_TransactionType = EventType\n}\n\n// Transactions_final_v1_2 table\n.create-merge table Transactions_final_v1_2 (\n BilledCost: real, // MS CM EA+MCA 2023-05-01\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n ChargeCategory: string, // Hubs add-on\n ChargeClass: string, // Hubs add-on\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n InvoiceId: string, // MS CM MCA 2023-05-01\n PricingQuantity: real, // MS CM EA+MCA 2023-05-01\n PricingUnit: string, // Hubs add-on\n ProviderName: string, // Hubs add-on\n RegionId: string, // MS CM EA+MCA 2023-05-01\n RegionName: string, // MS CM EA+MCA 2023-05-01\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\n x_AccountName: string, // MS CM EA 2023-05-01\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\n x_CostCenter: string, // MS CM EA 2023-05-01\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\n x_IngestionTime: datetime, // Hubs add-on\n x_MonetaryCommitment: real, // MS CM EA 2023-05-01\n x_Overage: real, // MS CM EA 2023-05-01\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\n)\n\n// Update policy for Transactions_raw -> Transactions_final_v1_2 table\n.alter table Transactions_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Transactions_raw\",\n \"Query\": \"Transactions_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n", + "$fxv#11": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Common utility functions\n//\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\n//======================================================================================================================\n\n\n//===| Date functions |=================================================================================================\n\n// monthstring\n.create-or-alter function \nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \nmonthstring(['date']: datetime, length: int = 9)\n{\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\n}\n\n// datestring\n.create-or-alter function \nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n let month = (d: datetime) { monthstring(d, 3) };\n let endDate = iff(end == datetime('0001-01-01'), start, end);\n let sameDate = startofday(start) == startofday(endDate);\n let sameMonth = startofmonth(start) == startofmonth(endDate);\n let sameYear = startofyear(start) == startofyear(endDate);\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\n let currentYear = sameYear and startofyear(start) == startofyear(now());\n case(\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\n fullYear,\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\n // 1 full mo, same year | Mmm yyyy\n fullMonth and sameMonth and sameYear,\n strcat(month(start), ' ', getyear(start)),\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\n fullMonth and sameYear,\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\n fullMonth and not(sameYear),\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\n sameDate,\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\n not(fullMonth) and sameMonth and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\n not(fullMonth) and not(sameMonth) and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\n )\n}\n\n// daterange\n.create-or-alter function \nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n datestring(start, end)\n}\n\n// monthsago\n.create-or-alter function \nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\nmonthsago(months: int)\n{\n datetime_add('month', -months, startofmonth(now()))\n}\n\n\n//===| Number functions |===============================================================================================\n// NOTE: Must be defined before string converters\n\n// delta\n.create-or-alter function \nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \ndelta(oldval: double, newval: double)\n{\n (newval - todouble(oldval))/oldval\n}\n\n// percentOfTotal\n// NOTE: Must be before percent() function\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercentOfTotal(t: (Count: long), tot: long)\n{\n let total = todouble(tot);\n t \n | extend Percent = round(Count / total * 100, 3) \n | order by Count desc\n}\n\n// percent\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercent(t: (Count: long))\n{\n let total = todouble(toscalar(t | summarize sum(Count)));\n percentOfTotal(t, total)\n}\n\n// plusminus\n.create-or-alter function \nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\nplusminus(val: string)\n{\n let neg = substring(val, 0, 1) == '-';\n iff(neg, val, strcat('+', val))\n}\n\n// updown\n.create-or-alter function \nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\nupdown(val: string)\n{\n // TODO: Handle 0\n let neg = substring(val, 0, 1) == '-';\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\n}\n\n\n//===| String functions |===============================================================================================\n\n// percentstring\n// NOTE: Must be defined before deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\npercentstring(num: double, total: double = 1.0, places: int = 9)\n{\n let value = 1.0 * num / total * 100;\n strcat(case(\n places != 9, round(value, places),\n value < 10, round(value, 2),\n round(value, 1)\n ), '%')\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// arraystring\n.create-or-alter function \nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\narraystring(arr: dynamic)\n{\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\n tostring(arr)\n , @'^\\[\"', '')\n , @'\"\\]$', '')\n , @'^, ', '')\n , @', $', '')\n , @'^\\[]$', '')\n , '\",\"', ', ')\n}\n\n// deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\n{\n let d = delta(oldval, newval);\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\n}\n\n// diffstring\n.create-or-alter function \nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\ndiffstring(oldval: double, newval: double, places: int = 1)\n{\n plusminus(round(newval - oldval, places))\n}\n\n// numberstring\n.create-or-alter function \nwith (docstring = 'Convert a number to a string', folder = 'Common')\nnumberstring(num: double, abbrev: bool = true)\n{\n replace_regex(case(\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\n tostring(num)\n ), @'\\.0$', '')\n}\n\n\n//===| Other |==========================================================================================================\n\n// ifempty\n.create-or-alter function \nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\nifempty(val: dynamic, defaultVal: dynamic)\n{\n iff(isempty(val), defaultVal, val)\n}\n", + "$fxv#12": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / Open data functions\n// Wrap Ingestion database tables for easy access.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// PricingUnits\n.create-or-alter function\nwith (docstring = 'Gets pricing units from the FinOps toolkit PricingUnits open data.', folder = 'OpenData')\nPricingUnits()\n{\n database('Ingestion').PricingUnits\n}\n\n// Regions\n.create-or-alter function\nwith (docstring = 'Gets regions from the FinOps toolkit Regions open data.', folder = 'OpenData')\nRegion()\n{\n database('Ingestion').Regions\n}\n\n// ResourceTypes\n.create-or-alter function\nwith (docstring = 'Gets resource types from the FinOps toolkit ResourceTypes open data.', folder = 'OpenData')\nResourceType()\n{\n database('Ingestion').ResourceTypes\n}\n\n// Services\n.create-or-alter function\nwith (docstring = 'Gets services from the FinOps toolkit Services open data.', folder = 'OpenData')\nServices()\n{\n database('Ingestion').Services\n}\n", + "$fxv#13": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / FOCUS 1.0 functions\n// Used for reporting with backward compatibility.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// CommitmentDiscountUsage_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.0.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage_v1_0()\n{\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\n | union (\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\n // Convert real to decimal\n | extend\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n x_CommitmentDiscountCommittedCount = todecimal(x_CommitmentDiscountCommittedCount),\n x_CommitmentDiscountCommittedAmount = todecimal(x_CommitmentDiscountCommittedAmount),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio)\n )\n | project\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountType,\n ConsumedQuantity,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SubAccountId,\n x_CommitmentDiscountCommittedCount,\n x_CommitmentDiscountCommittedAmount,\n x_CommitmentDiscountNormalizedGroup,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountQuantity,\n x_IngestionTime,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceModel,\n x_SkuOrderId,\n x_SkuSize,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Costs_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.0.', folder = 'Costs')\nCosts_v1_0()\n{\n database('Ingestion').Costs_final_v1_0\n | union (\n database('Ingestion').Costs_final_v1_2\n // Convert real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n ContractedCost = todecimal(ContractedCost),\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n EffectiveCost = todecimal(EffectiveCost),\n ListCost = todecimal(ListCost),\n ListUnitPrice = todecimal(ListUnitPrice),\n PricingQuantity = todecimal(PricingQuantity),\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\n // Rename columns\n | project-rename\n x_InvoiceId = InvoiceId,\n x_PricingCurrency = PricingCurrency,\n x_SkuMeterName = SkuMeter\n // Generate historical x_SkuDetails format from SkuPriceDetails\n | extend x_SkuDetails = iff(isnotempty(x_SkuDetails), x_SkuDetails, parse_json(replace_regex(tostring(SkuPriceDetails), @'([\\{,])\"x_', @'\\1\"')))\n )\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost,\n ContractedUnitPrice,\n EffectiveCost,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SkuId,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType,\n Tags,\n x_AccountId,\n x_AccountName,\n x_AccountOwnerId,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_ContractedCostInUsd,\n x_CostAllocationRuleName,\n x_CostCategories,\n x_CostCenter,\n x_Credits,\n x_CostType,\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount,\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InvoiceId,\n x_InvoiceIssuerId,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_ListCostInUsd,\n x_Location,\n x_Operation,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingCurrency,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuIsCreditEligible,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_UsageType\n}\n\n\n// Prices_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all prices aligned to FOCUS 1.0.', folder = 'Prices')\nPrices_v1_0()\n{\n database('Ingestion').Prices_final_v1_0\n | union (\n database('Ingestion').Prices_final_v1_2\n // Convert real to decimal\n | extend\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n ListUnitPrice = todecimal(ListUnitPrice),\n x_BaseUnitPrice = todecimal(x_BaseUnitPrice),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\n x_ContractedUnitPriceDiscount = todecimal(x_ContractedUnitPriceDiscount),\n x_ContractedUnitPriceDiscountPercent = todecimal(x_ContractedUnitPriceDiscountPercent),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_EffectiveUnitPriceDiscount = todecimal(x_EffectiveUnitPriceDiscount),\n x_EffectiveUnitPriceDiscountPercent = todecimal(x_EffectiveUnitPriceDiscountPercent),\n x_PricingBlockSize = todecimal(x_PricingBlockSize),\n x_SkuIncludedQuantity = todecimal(x_SkuIncludedQuantity),\n x_SkuTier = todecimal(x_SkuTier),\n x_TotalUnitPriceDiscount = todecimal(x_TotalUnitPriceDiscount),\n x_TotalUnitPriceDiscountPercent = todecimal(x_TotalUnitPriceDiscountPercent) \n // Rename columns\n | project-rename\n x_PricingCurrency = PricingCurrency,\n x_SkuMeterName = SkuMeter\n )\n | project\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType,\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory,\n PricingUnit,\n SkuId,\n SkuPriceId,\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent,\n x_EffectivePeriodEnd,\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingCurrency,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_SkuDescription,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent\n}\n\n\n// Recommendations_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.0.', folder = 'Recommendations')\nRecommendations_v1_0()\n{\n database('Ingestion').Recommendations_final_v1_0\n | union (\n database('Ingestion').Recommendations_final_v1_2\n // Convert real to decimal\n | extend\n x_EffectiveCostAfter = todecimal(x_EffectiveCostAfter),\n x_EffectiveCostBefore = todecimal(x_EffectiveCostBefore),\n x_EffectiveCostSavings = todecimal(x_EffectiveCostSavings)\n )\n | project\n ProviderName,\n SubAccountId,\n x_IngestionTime,\n x_EffectiveCostAfter,\n x_EffectiveCostBefore,\n x_EffectiveCostSavings,\n x_RecommendationDate,\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Transactions_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.0.', folder = 'Transactions')\nTransactions_v1_0()\n{\n database('Ingestion').Transactions_final_v1_0\n | union (\n database('Ingestion').Transactions_final_v1_2\n // Convert real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n PricingQuantity = todecimal(PricingQuantity),\n x_MonetaryCommitment = todecimal(x_MonetaryCommitment),\n x_Overage = todecimal(x_Overage)\n // Rename columns\n | project-rename\n x_InvoiceId = InvoiceId\n )\n | project\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodStart,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n RegionId,\n RegionName,\n SubAccountId,\n SubAccountName,\n x_AccountName,\n x_AccountOwnerId,\n x_CostCenter,\n x_InvoiceId,\n x_InvoiceNumber,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_IngestionTime,\n x_MonetaryCommitment,\n x_Overage,\n x_PurchasingBillingAccountId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuSize,\n x_SkuTerm,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId,\n x_TransactionType\n}\n", + "$fxv#14": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / FOCUS 1.2 functions\n// Used for reporting with backward compatibility.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// CommitmentDiscountUsage_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.2.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage_v1_2()\n{\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\n | union (\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\n // Convert decimal to real\n | extend\n ConsumedQuantity = toreal(ConsumedQuantity),\n x_CommitmentDiscountCommittedCount = toreal(x_CommitmentDiscountCommittedCount),\n x_CommitmentDiscountCommittedAmount = toreal(x_CommitmentDiscountCommittedAmount),\n x_CommitmentDiscountNormalizedRatio = toreal(x_CommitmentDiscountNormalizedRatio)\n // Add new columns\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceSubcategory) on x_ResourceType\n | extend CommitmentDiscountQuantity = ConsumedQuantity * x_CommitmentDiscountNormalizedRatio\n | extend CommitmentDiscountUnit = case(\n x_CommitmentDiscountNormalizedRatio == 1, 'Hours',\n x_CommitmentDiscountNormalizedRatio > 1, 'Normalized Hours',\n ''\n )\n )\n | project\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountQuantity,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SubAccountId,\n x_CommitmentDiscountCommittedCount,\n x_CommitmentDiscountCommittedAmount,\n x_CommitmentDiscountNormalizedGroup,\n x_CommitmentDiscountNormalizedRatio,\n x_IngestionTime,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceModel,\n x_SkuOrderId,\n x_SkuSize,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Costs_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.2.', folder = 'Costs')\nCosts_v1_2()\n{\n database('Ingestion').Costs_final_v1_2\n | union (\n database('Ingestion').Costs_final_v1_0\n // Convert decimal to real\n | extend\n BilledCost = toreal(BilledCost),\n ConsumedQuantity = toreal(ConsumedQuantity),\n ContractedCost = toreal(ContractedCost),\n ContractedUnitPrice = toreal(ContractedUnitPrice),\n EffectiveCost = toreal(EffectiveCost),\n ListCost = toreal(ListCost),\n ListUnitPrice = toreal(ListUnitPrice),\n PricingQuantity = toreal(PricingQuantity),\n x_BilledCostInUsd = toreal(x_BilledCostInUsd),\n x_BilledUnitPrice = toreal(x_BilledUnitPrice),\n x_BillingExchangeRate = toreal(x_BillingExchangeRate),\n x_ContractedCostInUsd = toreal(x_ContractedCostInUsd),\n x_CurrencyConversionRate = toreal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = toreal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\n x_ListCostInUsd = toreal(x_ListCostInUsd),\n x_PricingBlockSize = toreal(x_PricingBlockSize)\n // Rename columns\n | project-rename\n InvoiceId = x_InvoiceId,\n PricingCurrency = x_PricingCurrency,\n SkuMeter = x_SkuMeterName\n // Add new columns\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | extend CapacityReservationId = tostring(x_SkuDetails.VMCapacityReservationId)\n | extend CapacityReservationStatus = case(\n isempty(CapacityReservationId), '',\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\n 'Used'\n )\n | extend x_CommitmentDiscountNormalizedRatio = case(\n // Not applicable\n isempty(CommitmentDiscountStatus), real(null),\n // Parse from SKU details if not specified explicitly\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, dynamic(1)))\n )\n | extend CommitmentDiscountQuantity = case(\n isempty(CommitmentDiscountStatus), real(null),\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\n real(null)\n )\n | extend CommitmentDiscountUnit = case(\n isempty(CommitmentDiscountQuantity), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\n ''\n )\n | extend x_AmortizationClass = case(\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\n ''\n )\n // Hubs add-ons\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n | extend x_SkuCoreCount = toint(coalesce(x_SkuDetails.VCPUs, x_SkuDetails.VCores, x_SkuDetails.vCores))\n | extend x_SkuInstanceType = tostring(coalesce(x_SkuDetails.ServiceType, x_SkuDetails.ServerSku))\n | extend x_SkuOperatingSystem = case(\n x_SkuDetails.ImageType == 'Canonical', 'Linux',\n x_SkuDetails.ImageType == 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\n x_SkuDetails.ImageType\n )\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\n | extend tmp_SqlAhb = tolower(x_SkuDetails.AHB)\n | extend x_SkuLicenseType = case(\n x_SkuDetails.ImageType contains 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\n ''\n )\n | extend x_SkuLicenseStatus = case(\n isnotempty(x_SkuLicenseType) or tmp_SqlAhb == 'true' or (x_SkuMeterSubcategory contains 'Azure Hybrid Benefit'), 'Enabled',\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not enabled',\n ''\n )\n | extend x_SkuLicenseQuantity = case(\n isempty(x_SkuCoreCount), int(null),\n x_SkuCoreCount <= 8, int(8),\n x_SkuCoreCount > 8, x_SkuCoreCount,\n int(null)\n )\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\n | extend x_CommitmentDiscountSavings = iff(ContractedCost < EffectiveCost, real(0), ContractedCost - EffectiveCost)\n | extend x_NegotiatedDiscountSavings = iff(ListCost < ContractedCost, real(0), ListCost - ContractedCost)\n | extend x_TotalSavings = iff(ListCost < EffectiveCost, real(0), ListCost - EffectiveCost)\n | extend x_CommitmentDiscountPercent = iff(ContractedUnitPrice == 0, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\n | extend x_NegotiatedDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\n | extend x_TotalDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\n // SkuPriceDetails conversion -- Must be after hubs add-ons\n | extend SkuPriceDetails = parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\n // Prefix all keys with x_ first to avoid double-prefixing\n , @'([\\{,])\"', @'\\1\"x_')\n // CoreCount for number of CPUs/vCPUs/cores/vCores\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\n // TODO: DiskSpace for disk size in GiB\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\n // TODO: GpuCount for the number of GPUs\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\n // TODO: InstanceSeries for the size family/series\n // TODO: MemorySize for the RAM in GiB\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\n // OperatingSystem for the OS name\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\n )\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\n SkuPriceDetails)\n )\n | extend SkuPriceDetails = iff(isnotempty(SkuPriceDetails), SkuPriceDetails, parse_json(replace_regex(tostring(x_SkuDetails), @'([\\{,])\"', @'\\1\"x_')))\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n CapacityReservationId,\n CapacityReservationStatus,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountQuantity,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost,\n ContractedUnitPrice,\n EffectiveCost,\n InvoiceId,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId,\n SkuMeter,\n SkuPriceDetails,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType,\n Tags,\n x_AccountId,\n x_AccountName,\n x_AccountOwnerId,\n x_AmortizationClass,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingItemCode,\n x_BillingItemName,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountPercent,\n x_CommitmentDiscountSavings,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_CommitmentDiscountUtilizationAmount,\n x_CommitmentDiscountUtilizationPotential,\n x_CommodityCode,\n x_CommodityName,\n x_ComponentName,\n x_ComponentType,\n x_ConsumedCoreHours,\n x_ContractedCostInUsd,\n x_CostAllocationRuleName,\n x_CostCategories,\n x_CostCenter,\n x_CostType,\n x_Credits,\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount,\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InstanceID,\n x_InvoiceIssuerId,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_ListCostInUsd,\n x_Location,\n x_NegotiatedDiscountPercent,\n x_NegotiatedDiscountSavings,\n x_Operation,\n x_OwnerAccountID,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServiceModel,\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuCoreCount,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuInstanceType,\n x_SkuIsCreditEligible,\n x_SkuLicenseQuantity,\n x_SkuLicenseStatus,\n x_SkuLicenseType,\n x_SkuLicenseUnit,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOperatingSystem,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuPlanName,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceValues,\n x_SourceVersion,\n x_SubproductName,\n x_TotalDiscountPercent,\n x_TotalSavings,\n x_UsageType\n}\n\n\n// Prices_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all prices aligned to FOCUS 1.2.', folder = 'Prices')\nPrices_v1_2()\n{\n database('Ingestion').Prices_final_v1_2\n | union (\n database('Ingestion').Prices_final_v1_0\n // Convert decimal to real\n | extend\n ContractedUnitPrice = toreal(ContractedUnitPrice),\n ListUnitPrice = toreal(ListUnitPrice),\n x_BaseUnitPrice = toreal(x_BaseUnitPrice),\n x_ContractedUnitPriceDiscount = toreal(x_ContractedUnitPriceDiscount),\n x_ContractedUnitPriceDiscountPercent = toreal(x_ContractedUnitPriceDiscountPercent),\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\n x_EffectiveUnitPriceDiscount = toreal(x_EffectiveUnitPriceDiscount),\n x_EffectiveUnitPriceDiscountPercent = toreal(x_EffectiveUnitPriceDiscountPercent),\n x_PricingBlockSize = toreal(x_PricingBlockSize),\n x_SkuIncludedQuantity = toreal(x_SkuIncludedQuantity),\n x_SkuTier = toreal(x_SkuTier),\n x_TotalUnitPriceDiscount = toreal(x_TotalUnitPriceDiscount),\n x_TotalUnitPriceDiscountPercent = toreal(x_TotalUnitPriceDiscountPercent) \n // Rename columns\n | project-rename\n PricingCurrency = x_PricingCurrency,\n SkuMeter = x_SkuMeterName\n )\n | project\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingUnit,\n SkuId,\n SkuMeter,\n SkuPriceId,\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent,\n x_EffectivePeriodEnd,\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_SkuDescription,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent\n}\n\n\n// Recommendations_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.2.', folder = 'Recommendations')\nRecommendations_v1_2()\n{\n database('Ingestion').Recommendations_final_v1_2\n | union (\n database('Ingestion').Recommendations_final_v1_0\n // Convert decimal to real\n | extend\n x_EffectiveCostAfter = toreal(x_EffectiveCostAfter),\n x_EffectiveCostBefore = toreal(x_EffectiveCostBefore),\n x_EffectiveCostSavings = toreal(x_EffectiveCostSavings)\n )\n | project\n ProviderName,\n SubAccountId,\n x_IngestionTime,\n x_EffectiveCostAfter,\n x_EffectiveCostBefore,\n x_EffectiveCostSavings,\n x_RecommendationDate,\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Transactions_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.2.', folder = 'Transactions')\nTransactions_v1_2()\n{\n database('Ingestion').Transactions_final_v1_2\n | union (\n database('Ingestion').Transactions_final_v1_0\n // Convert decimal to real\n | extend\n BilledCost = toreal(BilledCost),\n PricingQuantity = toreal(PricingQuantity),\n x_MonetaryCommitment = toreal(x_MonetaryCommitment),\n x_Overage = toreal(x_Overage)\n // Rename columns\n | project-rename\n InvoiceId = x_InvoiceId\n )\n | project\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodStart,\n InvoiceId,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n RegionId,\n RegionName,\n SubAccountId,\n SubAccountName,\n x_AccountName,\n x_AccountOwnerId,\n x_CostCenter,\n x_InvoiceNumber,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_IngestionTime,\n x_MonetaryCommitment,\n x_Overage,\n x_PurchasingBillingAccountId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuSize,\n x_SkuTerm,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId,\n x_TransactionType\n}\n\n\n//======================================================================================================================\n// Latest FOCUS version\n//======================================================================================================================\n\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage()\n{\n CommitmentDiscountUsage_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\nCosts()\n{\n Costs_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\nPrices()\n{\n Prices_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\nRecommendations()\n{\n Recommendations_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\nTransactions()\n{\n Transactions_v1_2()\n}\n", + "$fxv#15": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / Latest FOCUS version functions\n// Used for ad hoc queries.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage()\n{\n CommitmentDiscountUsage_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\nCosts()\n{\n Costs_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\nPrices()\n{\n Prices_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\nRecommendations()\n{\n Recommendations_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\nTransactions()\n{\n Transactions_v1_2()\n}\n", + "$fxv#2": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_3(id: string) {\n dynamic({\n \"microsoft.hybridnetwork/vendors\": { \"SingularDisplayName\": \"Azure Network Function Manager ? vendor\" }\n ,\"microsoft.hybridonboarding/extensionmanagers\": { \"SingularDisplayName\": \"Microsoft.HybridOnboarding extension manager\" }\n ,\"microsoft.impact/connectors\": { \"SingularDisplayName\": \"Impact Reporting Connector\" }\n ,\"microsoft.impact/impactcategories\": { \"SingularDisplayName\": \"Microsoft.Impact impact category\" }\n ,\"microsoft.impact/topologyimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact topology impact\" }\n ,\"microsoft.impact/workloadimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact workload impact\" }\n ,\"microsoft.impact/workloadimpacts/insights\": { \"SingularDisplayName\": \"Microsoft.Impact workload impacts insight\" }\n ,\"microsoft.importexport/jobs\": { \"SingularDisplayName\": \"Microsoft.ImportExport job\" }\n ,\"microsoft.insights/actiongroups\": { \"SingularDisplayName\": \"Action group\" }\n ,\"microsoft.insights/activitylogalerts\": { \"SingularDisplayName\": \"Activity log alert rule\" }\n ,\"microsoft.insights/alertrules\": { \"SingularDisplayName\": \"Microsoft.Insights alertrule\" }\n ,\"microsoft.insights/alertrules/incidents\": { \"SingularDisplayName\": \"Microsoft.insights alertrules incident\" }\n ,\"microsoft.insights/autoscalesettings\": { \"SingularDisplayName\": \"Microsoft.Insights autoscalesetting\" }\n ,\"microsoft.insights/components\": { \"SingularDisplayName\": \"Application Insights app\" }\n ,\"microsoft.insights/datacollectionendpoints\": { \"SingularDisplayName\": \"Data collection endpoint\" }\n ,\"microsoft.insights/datacollectionruleassociations\": { \"SingularDisplayName\": \"Microsoft.Insights data collection rule association\" }\n ,\"microsoft.insights/datacollectionrules\": { \"SingularDisplayName\": \"Data collection rule\" }\n ,\"microsoft.insights/datacollectionrulesresources\": { \"SingularDisplayName\": \"Data collection rule associated resource\" }\n ,\"microsoft.insights/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\n ,\"microsoft.insights/diagnosticsettingscategories\": { \"SingularDisplayName\": \"Microsoft.Insights diagnostic settings category\" }\n ,\"microsoft.insights/guestdiagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic setting\" }\n ,\"microsoft.insights/guestdiagnosticsettingsassociation\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic settings association\" }\n ,\"microsoft.insights/logprofiles\": { \"SingularDisplayName\": \"Microsoft.Insights logprofile\" }\n ,\"microsoft.insights/metricalerts\": { \"SingularDisplayName\": \"Metric alert rule\" }\n ,\"microsoft.insights/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights notification statu\" }\n ,\"microsoft.insights/privatelinkscopeoperationstatuses\": { \"SingularDisplayName\": \"Microsoft.insights private link scope operation statuse\" }\n ,\"microsoft.insights/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Monitor Private Link Scope\" }\n ,\"microsoft.insights/scheduledqueryrules\": { \"SingularDisplayName\": \"Log search alert rule\" }\n ,\"microsoft.insights/tenantactiongroups\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action group\" }\n ,\"microsoft.insights/tenantactiongroups/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action groups notification statu\" }\n ,\"microsoft.insights/vminsightsonboardingstatuses\": { \"SingularDisplayName\": \"Microsoft.Insights VM insights onboarding statuse\" }\n ,\"microsoft.insights/webtests\": { \"SingularDisplayName\": \"Application Insights availability test\" }\n ,\"microsoft.insights/workbooks\": { \"SingularDisplayName\": \"Azure Workbook\" }\n ,\"microsoft.insights/workbooktemplates\": { \"SingularDisplayName\": \"Azure Workbook Template\" }\n ,\"microsoft.integrationspaces/spaces\": { \"SingularDisplayName\": \"Integration Environment\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twin\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/assets\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins asset\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/executionplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins execution plan\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/testplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test plan\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/tests\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test\" }\n ,\"microsoft.inventory/subscriptioninternalproperties\": { \"SingularDisplayName\": \"Microsoft.Inventory subscription internal property\" }\n ,\"microsoft.iotcentral/iotapps\": { \"SingularDisplayName\": \"IoT Central Application\" }\n ,\"microsoft.iotfirmwaredefense/workspaces\": { \"SingularDisplayName\": \"Firmware analysis workspace\" }\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmware\" }\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares/summaries\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmwares summary\" }\n ,\"microsoft.iotoperations/instances\": { \"SingularDisplayName\": \"Azure IoT Operations\" }\n ,\"microsoft.iotoperations/instances/brokers\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances broker\" }\n ,\"microsoft.iotoperations/instances/brokers/authentications\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authentication\" }\n ,\"microsoft.iotoperations/instances/brokers/authorizations\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authorization\" }\n ,\"microsoft.iotoperations/instances/brokers/listeners\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers listener\" }\n ,\"microsoft.iotoperations/instances/dataflowendpoints\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow endpoint\" }\n ,\"microsoft.iotoperations/instances/dataflowprofiles\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profile\" }\n ,\"microsoft.iotoperations/instances/dataflowprofiles/dataflows\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profiles dataflow\" }\n ,\"microsoft.iotoperationsdataprocessor/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instance\" }\n ,\"microsoft.iotoperationsdataprocessor/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances dataset\" }\n ,\"microsoft.iotoperationsdataprocessor/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances pipeline\" }\n ,\"microsoft.iotoperationsmq/mq\": { \"SingularDisplayName\": \"IoT Operations Ops MQ\" }\n ,\"microsoft.iotoperationsmq/mq/broker\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker\" }\n ,\"microsoft.iotoperationsmq/mq/broker/authentication\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authentication\" }\n ,\"microsoft.iotoperationsmq/mq/broker/authorization\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authorization\" }\n ,\"microsoft.iotoperationsmq/mq/broker/listener\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker listener\" }\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector\" }\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector topic map\" }\n ,\"microsoft.iotoperationsmq/mq/diagnosticservice\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq diagnostic service\" }\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector\" }\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector topic map\" }\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector\" }\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector topic map\" }\n ,\"microsoft.iotoperationsorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator instance\" }\n ,\"microsoft.iotoperationsorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator solution\" }\n ,\"microsoft.iotoperationsorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator target\" }\n ,\"microsoft.iotsecurity/alerttypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity alert type\" }\n ,\"microsoft.iotsecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity defender setting\" }\n ,\"microsoft.iotsecurity/onpremisesensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity on premise sensor\" }\n ,\"microsoft.iotsecurity/recommendationtypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity recommendation type\" }\n ,\"microsoft.iotsecurity/sensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity sensor\" }\n ,\"microsoft.iotsecurity/sites\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity site\" }\n ,\"microsoft.keyvault/managedhsms\": { \"SingularDisplayName\": \"Azure Key Vault Managed HSM\" }\n ,\"microsoft.keyvault/vaults\": { \"SingularDisplayName\": \"Key vault\" }\n ,\"microsoft.kubernetes/connectedclusters\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc extension\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc namespace\" }\n ,\"microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\n ,\"microsoft.kubernetesconfiguration/extensiontypes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension type\" }\n ,\"microsoft.kubernetesconfiguration/extensiontypes/versions\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension types version\" }\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configuration\" }\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations/operations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configurations operation\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scope\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private endpoint connection\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private link resource\" }\n ,\"microsoft.kubernetesconfiguration/sourcecontrolconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration source control configuration\" }\n ,\"microsoft.kubernetesruntime/bgppeers\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime bgp peer\" }\n ,\"microsoft.kubernetesruntime/loadbalancers\": { \"SingularDisplayName\": \"Arc Load Balancer\" }\n ,\"microsoft.kubernetesruntime/services\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime service\" }\n ,\"microsoft.kubernetesruntime/storageclasses\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime storage class\" }\n ,\"microsoft.kusto/clusters\": { \"SingularDisplayName\": \"Azure Data Explorer Cluster\" }\n ,\"microsoft.kusto/clusters/databases\": { \"SingularDisplayName\": \"Azure Data Explorer Database\" }\n ,\"microsoft.labservices/labaccounts\": { \"SingularDisplayName\": \"Lab account\" }\n ,\"microsoft.labservices/labaccounts/labs\": { \"SingularDisplayName\": \"Lab\" }\n ,\"microsoft.labservices/labplans\": { \"SingularDisplayName\": \"Lab plan\" }\n ,\"microsoft.labservices/labs\": { \"SingularDisplayName\": \"Lab\" }\n ,\"microsoft.liftrpilot/organizations\": { \"SingularDisplayName\": \"Azure Pilot\" }\n ,\"microsoft.loadtestservice/loadtestmappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test mapping\" }\n ,\"microsoft.loadtestservice/loadtestprofilemappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test profile mapping\" }\n ,\"microsoft.loadtestservice/loadtests\": { \"SingularDisplayName\": \"Azure Load Testing\" }\n ,\"microsoft.loadtestservice/playwrightworkspaces\": { \"SingularDisplayName\": \"Playwright Workspace\" }\n ,\"microsoft.logic/businessprocesses\": { \"SingularDisplayName\": \"Business Process\" }\n ,\"microsoft.logic/integrationaccounts\": { \"SingularDisplayName\": \"Logic app integration account\" }\n ,\"microsoft.logic/integrationserviceenvironments\": { \"SingularDisplayName\": \"Integration Service Environment\" }\n ,\"microsoft.logic/integrationserviceenvironments/health\": { \"SingularDisplayName\": \"Microsoft.Logic integration service environments health\" }\n ,\"microsoft.logic/integrationserviceenvironments/managedapis\": { \"SingularDisplayName\": \"Managed Connector\" }\n ,\"microsoft.logic/templates\": { \"SingularDisplayName\": \"Logic Apps Template\" }\n ,\"microsoft.logic/workflows\": { \"SingularDisplayName\": \"Logic app\" }\n ,\"microsoft.logz/monitors\": { \"SingularDisplayName\": \"Logz.io\" }\n ,\"microsoft.logz/monitors/accounts\": { \"SingularDisplayName\": \"Logz sub account\" }\n ,\"microsoft.m365/m365resources\": { \"SingularDisplayName\": \"Microsoft.M365 m365 resource\" }\n ,\"microsoft.m365consumptionservices/services\": { \"SingularDisplayName\": \"Microsoft.M365ConsumptionServices service\" }\n ,\"microsoft.machinelearning/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plan\" }\n ,\"microsoft.machinelearning/commitmentplans/commitmentassociations\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plans commitment association\" }\n ,\"microsoft.machinelearning/webservices\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) web service\" }\n ,\"microsoft.machinelearning/workspaces\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) workspace\" }\n ,\"microsoft.machinelearningexperimentation/accounts\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation account\" }\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspace\" }\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspaces project\" }\n ,\"microsoft.machinelearningservices/aistudio\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.machinelearningservices/aistudiocreate\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.machinelearningservices/registries\": { \"SingularDisplayName\": \"Azure Machine Learning registry\" }\n ,\"microsoft.machinelearningservices/workspaces\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints\": { \"SingularDisplayName\": \"Machine learning online endpoint\" }\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints/deployments\": { \"SingularDisplayName\": \"Machine learning online deployment\" }\n ,\"microsoft.machinelearningservices/workspacescreate\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\n ,\"microsoft.maintenance/configurationassignments\": { \"SingularDisplayName\": \"Microsoft.Maintenance configuration assignment\" }\n ,\"microsoft.maintenance/maintenanceconfigurations\": { \"SingularDisplayName\": \"Maintenance Configuration\" }\n ,\"microsoft.maintenance/maintenanceconfigurationsaumbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\n ,\"microsoft.maintenance/maintenanceconfigurationsbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\n ,\"microsoft.maintenance/publicmaintenanceconfigurations\": { \"SingularDisplayName\": \"Microsoft.Maintenance public maintenance configuration\" }\n ,\"microsoft.managedidentity/identities\": { \"SingularDisplayName\": \"Microsoft.ManagedIdentity identity\" }\n ,\"microsoft.managedidentity/userassignedidentities\": { \"SingularDisplayName\": \"Managed Identity\" }\n ,\"microsoft.managednetwork/managednetworks\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed network\" }\n ,\"microsoft.managednetwork/managednetworks/managednetworkgroups\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network group\" }\n ,\"microsoft.managednetwork/managednetworks/managednetworkpeeringpolicies\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network peering policy\" }\n ,\"microsoft.managednetworkfabric/accesscontrollists\": { \"SingularDisplayName\": \"Access Control List (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/internetgatewayrules\": { \"SingularDisplayName\": \"Internet Gateway Rule (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/internetgateways\": { \"SingularDisplayName\": \"Internet Gateway (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipcommunities\": { \"SingularDisplayName\": \"IP Community (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipextendedcommunities\": { \"SingularDisplayName\": \"IP Extended Community (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipprefixes\": { \"SingularDisplayName\": \"IP Prefix (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l2isolationdomains\": { \"SingularDisplayName\": \"Layer 2 Isolation Domain (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains\": { \"SingularDisplayName\": \"Layer 3 Isolation Domain (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains/externalnetworks\": { \"SingularDisplayName\": \"External Network (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains/internalnetworks\": { \"SingularDisplayName\": \"Internal Network (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/neighborgroups\": { \"SingularDisplayName\": \"Neighbor Group (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkdevices\": { \"SingularDisplayName\": \"Network Device (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkdevices/networkinterfaces\": { \"SingularDisplayName\": \"Network Interface (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabriccontrollers\": { \"SingularDisplayName\": \"Network Fabric Controller (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabrics\": { \"SingularDisplayName\": \"Network Fabric (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabrics/networktonetworkinterconnects\": { \"SingularDisplayName\": \"Network to Network Interconnect (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabricskus\": { \"SingularDisplayName\": \"Network Fabric SKU (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkmonitors\": { \"SingularDisplayName\": \"Microsoft.ManagedNetworkFabric network monitor\" }\n ,\"microsoft.managednetworkfabric/networkpacketbrokers\": { \"SingularDisplayName\": \"Network Packet Broker (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkracks\": { \"SingularDisplayName\": \"Network Rack (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networktaprules\": { \"SingularDisplayName\": \"Network Tap Rule (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networktaps\": { \"SingularDisplayName\": \"Network Tap (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/routepolicies\": { \"SingularDisplayName\": \"Route Policy (Operator Nexus)\" }\n ,\"microsoft.managedservices/marketplaceregistrationdefinitions\": { \"SingularDisplayName\": \"Microsoft.ManagedServices marketplace registration definition\" }\n ,\"microsoft.managedservices/registrationassignments\": { \"SingularDisplayName\": \"Microsoft.ManagedServices registration assignment\" }\n ,\"microsoft.managedservices/registrationdefinitions\": { \"SingularDisplayName\": \"Azure Lighthouse\" }\n ,\"microsoft.management/managementgroups\": { \"SingularDisplayName\": \"Microsoft.Management management group\" }\n ,\"microsoft.management/managementgroups/microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\n ,\"microsoft.management/managementgroups/providers/privatelinkassociations\": { \"SingularDisplayName\": \"Application Gateway\" }\n ,\"microsoft.management/managementgroups/providers/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\n ,\"microsoft.management/managementgroups/settings\": { \"SingularDisplayName\": \"Microsoft.Management management groups setting\" }\n ,\"microsoft.management/managementgroups/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Management management groups subscription\" }\n ,\"microsoft.management/servicegroups\": { \"SingularDisplayName\": \"Service group\" }\n ,\"microsoft.managementpartner/partners\": { \"SingularDisplayName\": \"Microsoft.ManagementPartner partner\" }\n ,\"microsoft.manufacturingplatform/manufacturingdataservices\": { \"SingularDisplayName\": \"Factory Operations Agent in Azure AI Foundry\" }\n ,\"microsoft.maps/accounts\": { \"SingularDisplayName\": \"Azure Maps Account\" }\n ,\"microsoft.maps/accounts/creators\": { \"SingularDisplayName\": \"Azure Maps Creator Resource\" }\n ,\"microsoft.marketplace/privatestores\": { \"SingularDisplayName\": \"Microsoft.Marketplace private store\" }\n ,\"microsoft.marketplace/privatestores/adminrequestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores admin request approval\" }\n ,\"microsoft.marketplace/privatestores/collections\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collection\" }\n ,\"microsoft.marketplace/privatestores/collections/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collections offer\" }\n ,\"microsoft.marketplace/privatestores/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores offer\" }\n ,\"microsoft.marketplace/privatestores/requestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores request approval\" }\n ,\"microsoft.media/mediaservices\": { \"SingularDisplayName\": \"Media service\" }\n ,\"microsoft.media/mediaservices/accountfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services account filter\" }\n ,\"microsoft.media/mediaservices/assets\": { \"SingularDisplayName\": \"Microsoft.Media media services asset\" }\n ,\"microsoft.media/mediaservices/assets/assetfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services assets asset filter\" }\n ,\"microsoft.media/mediaservices/assets/tracks\": { \"SingularDisplayName\": \"Microsoft.Media media services assets track\" }\n ,\"microsoft.media/mediaservices/assets/tracks/operationresults\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation result\" }\n ,\"microsoft.media/mediaservices/assets/tracks/operationstatuses\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation statuse\" }\n ,\"microsoft.media/mediaservices/contentkeypolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services content key policy\" }\n ,\"microsoft.media/mediaservices/liveevents\": { \"SingularDisplayName\": \"Live event\" }\n ,\"microsoft.media/mediaservices/liveevents/liveoutputs\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices live events live output\" }\n ,\"microsoft.media/mediaservices/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private endpoint connection\" }\n ,\"microsoft.media/mediaservices/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private link resource\" }\n ,\"microsoft.media/mediaservices/streamingendpoints\": { \"SingularDisplayName\": \"Streaming Endpoint\" }\n ,\"microsoft.media/mediaservices/streaminglocators\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming locator\" }\n ,\"microsoft.media/mediaservices/streamingpolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming policy\" }\n ,\"microsoft.media/mediaservices/transforms\": { \"SingularDisplayName\": \"Microsoft.Media media services transform\" }\n ,\"microsoft.media/mediaservices/transforms/jobs\": { \"SingularDisplayName\": \"Microsoft.Media media services transforms job\" }\n ,\"microsoft.mesh/worlds\": { \"SingularDisplayName\": \"Microsoft.Mesh world\" }\n ,\"microsoft.mesh/worlds/events\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds event\" }\n ,\"microsoft.mesh/worlds/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds events access policy\" }\n ,\"microsoft.mesh/worlds/spaces\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds space\" }\n ,\"microsoft.mesh/worlds/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds spaces access policy\" }\n ,\"microsoft.mesh/worlds/templates\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds template\" }\n ,\"microsoft.mesh/worlds/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds templates access policy\" }\n ,\"microsoft.messagingcatalog/catalogs\": { \"SingularDisplayName\": \"Microsoft.MessagingCatalog catalog\" }\n ,\"microsoft.messagingconnectors/connectors\": { \"SingularDisplayName\": \"Microsoft.MessagingConnectors connector\" }\n ,\"microsoft.metaverse/metaverses\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverse\" }\n ,\"microsoft.metaverse/metaverses/events\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses event\" }\n ,\"microsoft.metaverse/metaverses/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses events access policy\" }\n ,\"microsoft.metaverse/metaverses/spaces\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses space\" }\n ,\"microsoft.metaverse/metaverses/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses spaces access policy\" }\n ,\"microsoft.metaverse/metaverses/templates\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses template\" }\n ,\"microsoft.metaverse/metaverses/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses templates access policy\" }\n ,\"microsoft.migrate/assessmentprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment project\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/clusters\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments cluster\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/assessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment\" }\n ,\"microsoft.migrate/assessmentprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/assessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments avs assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business case\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/avssummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases avs summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedavsmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated avs machine\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated machine\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedsqlentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated sql entity\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated web app\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/iaassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases iaas summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/overviewsummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases overview summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/paassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases paas summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects group\" }\n ,\"microsoft.migrate/assessmentprojects/groups/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessments assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessments avs assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql database\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql instance\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/recommendedassessedentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments recommended assessed entity\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments web app service plan\" }\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessment\" }\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/hypervcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects hypervcollector\" }\n ,\"microsoft.migrate/assessmentprojects/importcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects importcollector\" }\n ,\"microsoft.migrate/assessmentprojects/importsqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects import sql collector\" }\n ,\"microsoft.migrate/assessmentprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects machine\" }\n ,\"microsoft.migrate/assessmentprojects/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private endpoint connection\" }\n ,\"microsoft.migrate/assessmentprojects/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private link resource\" }\n ,\"microsoft.migrate/assessmentprojects/projectsummary\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects project summary\" }\n ,\"microsoft.migrate/assessmentprojects/servercollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects servercollector\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql database\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql instance\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql machine\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/sqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sqlcollector\" }\n ,\"microsoft.migrate/assessmentprojects/vmwarecollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects vmwarecollector\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments web app service plan\" }\n ,\"microsoft.migrate/assessmentprojects/webappcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app collector\" }\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessment\" }\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessments summary\" }\n ,\"microsoft.migrate/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate project\" }\n ,\"microsoft.migrate/migrateprojects/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database instance\" }\n ,\"microsoft.migrate/migrateprojects/databases\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database\" }\n ,\"microsoft.migrate/migrateprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects machine\" }\n ,\"microsoft.migrate/migrateprojects/migrateevents\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects migrate event\" }\n ,\"microsoft.migrate/migrateprojects/solutions\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects solution\" }\n ,\"microsoft.migrate/modernizeprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize project\" }\n ,\"microsoft.migrate/modernizeprojects/deployedresources\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects deployed resource\" }\n ,\"microsoft.migrate/modernizeprojects/jobs\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects job\" }\n ,\"microsoft.migrate/modernizeprojects/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects jobs operation\" }\n ,\"microsoft.migrate/modernizeprojects/migrateagents\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agent\" }\n ,\"microsoft.migrate/modernizeprojects/migrateagents/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agents operation\" }\n ,\"microsoft.migrate/modernizeprojects/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects operation\" }\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployment\" }\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployments operation\" }\n ,\"microsoft.migrate/modernizeprojects/workloadinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instance\" }\n ,\"microsoft.migrate/modernizeprojects/workloadinstances/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instances operation\" }\n ,\"microsoft.migrate/movecollections\": { \"SingularDisplayName\": \"Microsoft.Migrate move collection\" }\n ,\"microsoft.migrate/movecollections/moveresources\": { \"SingularDisplayName\": \"Microsoft.Migrate move collections move resource\" }\n ,\"microsoft.migrate/projects\": { \"SingularDisplayName\": \"Migration project\" }\n ,\"microsoft.mission/approvals\": { \"SingularDisplayName\": \"Approval\" }\n ,\"microsoft.mission/catalogs\": { \"SingularDisplayName\": \"Catalog\" }\n ,\"microsoft.mission/communities\": { \"SingularDisplayName\": \"Community\" }\n ,\"microsoft.mission/communities/communityendpoints\": { \"SingularDisplayName\": \"Community endpoint\" }\n ,\"microsoft.mission/communities/transithubs\": { \"SingularDisplayName\": \"Transit hub\" }\n ,\"microsoft.mission/enclaveconnections\": { \"SingularDisplayName\": \"Enclave connection\" }\n ,\"microsoft.mission/externalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission external connection\" }\n ,\"microsoft.mission/internalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission internal connection\" }\n ,\"microsoft.mission/virtualenclaves\": { \"SingularDisplayName\": \"Enclave\" }\n ,\"microsoft.mission/virtualenclaves/enclaveendpoints\": { \"SingularDisplayName\": \"Enclave endpoint\" }\n ,\"microsoft.mission/virtualenclaves/endpoints\": { \"SingularDisplayName\": \"Endpoint\" }\n ,\"microsoft.mission/virtualenclaves/workloads\": { \"SingularDisplayName\": \"Workload\" }\n ,\"microsoft.mixedreality/objectanchorsaccounts\": { \"SingularDisplayName\": \"Object Anchors Account\" }\n ,\"microsoft.mixedreality/objectunderstandingaccounts\": { \"SingularDisplayName\": \"Object Understanding Account\" }\n ,\"microsoft.mixedreality/remoterenderingaccounts\": { \"SingularDisplayName\": \"Remote Rendering Account\" }\n ,\"microsoft.mixedreality/spatialanchorsaccounts\": { \"SingularDisplayName\": \"Spatial Anchors Account\" }\n ,\"microsoft.mixedreality/spatialmapsaccounts\": { \"SingularDisplayName\": \"Microsoft.MixedReality spatial maps account\" }\n ,\"microsoft.mobilenetwork/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork amf deployment\" }\n ,\"microsoft.mobilenetwork/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork cluster service\" }\n ,\"microsoft.mobilenetwork/mobilenetworks\": { \"SingularDisplayName\": \"Mobile Network\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/datanetworks\": { \"SingularDisplayName\": \"Data Network\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/services\": { \"SingularDisplayName\": \"Service\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/simpolicies\": { \"SingularDisplayName\": \"SIM Policy\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/sites\": { \"SingularDisplayName\": \"Mobile Network Site\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/slices\": { \"SingularDisplayName\": \"Slice\" }\n ,\"microsoft.mobilenetwork/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nrf deployment\" }\n ,\"microsoft.mobilenetwork/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nssf deployment\" }\n ,\"microsoft.mobilenetwork/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork observability service\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes\": { \"SingularDisplayName\": \"Packet Core Control Plane\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes\": { \"SingularDisplayName\": \"Packet Core Data Plane\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes/attacheddatanetworks\": { \"SingularDisplayName\": \"Attached Data Network\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplaneversions\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork packet core control plane version\" }\n ,\"microsoft.mobilenetwork/radioaccessnetworks\": { \"SingularDisplayName\": \"Radio Access Network Insights\" }\n ,\"microsoft.mobilenetwork/sdmdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sdm deployment\" }\n ,\"microsoft.mobilenetwork/simgroups\": { \"SingularDisplayName\": \"SIM Group\" }\n ,\"microsoft.mobilenetwork/simgroups/sims\": { \"SingularDisplayName\": \"SIM\" }\n ,\"microsoft.mobilenetwork/sims\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sim\" }\n ,\"microsoft.mobilenetwork/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork smf deployment\" }\n ,\"microsoft.mobilenetwork/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork upf deployment\" }\n ,\"microsoft.mobilenetwork/virtualizedmmedeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork virtualized mme deployment\" }\n ,\"microsoft.mobilenetwork/vnfagentdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork vnf agent deployment\" }\n ,\"microsoft.mobilepacketcore/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore amf deployment\" }\n ,\"microsoft.mobilepacketcore/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore cluster service\" }\n ,\"microsoft.mobilepacketcore/networkfunctions\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore network function\" }\n ,\"microsoft.mobilepacketcore/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nrf deployment\" }\n ,\"microsoft.mobilepacketcore/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nssf deployment\" }\n ,\"microsoft.mobilepacketcore/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore observability service\" }\n ,\"microsoft.mobilepacketcore/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore smf deployment\" }\n ,\"microsoft.mobilepacketcore/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore upf deployment\" }\n ,\"microsoft.modsimworkbench/workbenches\": { \"SingularDisplayName\": \"Modeling and Simulation Workbench\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers\": { \"SingularDisplayName\": \"Chamber\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/connectors\": { \"SingularDisplayName\": \"Chamber Connector\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/filerequests\": { \"SingularDisplayName\": \"Chamber Data Pipeline File Request\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/files\": { \"SingularDisplayName\": \"Chamber Data Pipeline File\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/licenses\": { \"SingularDisplayName\": \"Chamber License\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/storages\": { \"SingularDisplayName\": \"Chamber Storage\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/workloads\": { \"SingularDisplayName\": \"Chamber VM\" }\n ,\"microsoft.modsimworkbench/workbenches/sharedstorages\": { \"SingularDisplayName\": \"Shared Storage\" }\n ,\"microsoft.monitor/accounts\": { \"SingularDisplayName\": \"Azure Monitor workspace\" }\n ,\"microsoft.monitor/investigations\": { \"SingularDisplayName\": \"Microsoft.Monitor investigation\" }\n ,\"microsoft.monitor/pipelinegroups\": { \"SingularDisplayName\": \"Azure Monitor pipeline\" }\n ,\"microsoft.mysqldiscovery/mysqlsites\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsite\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites agent\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites error summary\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/mysqlservers\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites my sqlserver\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/summaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites summary\" }\n ,\"microsoft.netapp/netappaccounts\": { \"SingularDisplayName\": \"NetApp account\" }\n ,\"microsoft.netapp/netappaccounts/backuppolicies\": { \"SingularDisplayName\": \"Backup Policy\" }\n ,\"microsoft.netapp/netappaccounts/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools\": { \"SingularDisplayName\": \"Capacity pool\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes\": { \"SingularDisplayName\": \"Volume\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/volumequotarules\": { \"SingularDisplayName\": \"User and group quota\" }\n ,\"microsoft.netapp/netappaccounts/snapshotpolicies\": { \"SingularDisplayName\": \"Snapshot policy\" }\n ,\"microsoft.netapp/netappaccounts/volumegroups\": { \"SingularDisplayName\": \"VolumeGroup\" }\n ,\"microsoft.network/applicationgatewayavailablessloptions\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl option\" }\n ,\"microsoft.network/applicationgatewayavailablessloptions/predefinedpolicies\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl options predefined policy\" }\n ,\"microsoft.network/applicationgateways\": { \"SingularDisplayName\": \"Application gateway\" }\n ,\"microsoft.network/applicationgatewaywebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Application Gateway WAF policy\" }\n ,\"microsoft.network/applicationsecuritygroups\": { \"SingularDisplayName\": \"Application security group\" }\n ,\"microsoft.network/azurefirewalls\": { \"SingularDisplayName\": \"Firewall\" }\n ,\"microsoft.network/azurewebcategories\": { \"SingularDisplayName\": \"Microsoft.Network Azure web category\" }\n ,\"microsoft.network/bastionhosts\": { \"SingularDisplayName\": \"Bastion\" }\n ,\"microsoft.network/cloudserviceslots\": { \"SingularDisplayName\": \"Microsoft.Network cloud service slot\" }\n ,\"microsoft.network/connections\": { \"SingularDisplayName\": \"Connection\" }\n ,\"microsoft.network/customipprefixes\": { \"SingularDisplayName\": \"Custom IP Prefix\" }\n ,\"microsoft.network/ddoscustompolicies\": { \"SingularDisplayName\": \"Microsoft.Network DDoS custom policy\" }\n ,\"microsoft.network/ddosprotectionplans\": { \"SingularDisplayName\": \"DDoS protection plan\" }\n ,\"microsoft.network/dnsforwardingrulesets\": { \"SingularDisplayName\": \"DNS forwarding ruleset\" }\n ,\"microsoft.network/dnsresolverdomainlists\": { \"SingularDisplayName\": \"DNS Domain List\" }\n ,\"microsoft.network/dnsresolverpolicies\": { \"SingularDisplayName\": \"DNS Security Policy\" }\n ,\"microsoft.network/dnsresolvers\": { \"SingularDisplayName\": \"DNS private resolver\" }\n ,\"microsoft.network/dnszones\": { \"SingularDisplayName\": \"DNS zone\" }\n ,\"microsoft.network/dscpconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network DSCP configuration\" }\n ,\"microsoft.network/expressroutecircuits\": { \"SingularDisplayName\": \"ExpressRoute circuit\" }\n ,\"microsoft.network/expressroutecrossconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connection\" }\n ,\"microsoft.network/expressroutecrossconnections/peerings\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connections peering\" }\n ,\"microsoft.network/expressroutegateways\": { \"SingularDisplayName\": \"ExpressRoute Gateway\" }\n ,\"microsoft.network/expressroutegateways/expressrouteconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route gateways express route connection\" }\n ,\"microsoft.network/expressrouteports\": { \"SingularDisplayName\": \"ExpressRoute Direct\" }\n ,\"microsoft.network/expressrouteportslocations\": { \"SingularDisplayName\": \"Microsoft.Network express route ports location\" }\n ,\"microsoft.network/firewallpolicies\": { \"SingularDisplayName\": \"Firewall Policy\" }\n ,\"microsoft.network/frontdoors\": { \"SingularDisplayName\": \"Front Door and CDN profiles\" }\n ,\"microsoft.network/frontdoorwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Front Door WAF policy\" }\n ,\"microsoft.network/ipallocations\": { \"SingularDisplayName\": \"Microsoft.Network IP allocation\" }\n ,\"microsoft.network/ipgroups\": { \"SingularDisplayName\": \"IP Group\" }\n ,\"microsoft.network/loadbalancers\": { \"SingularDisplayName\": \"Load balancer\" }\n ,\"microsoft.network/localnetworkgateways\": { \"SingularDisplayName\": \"Local network gateway\" }\n ,\"microsoft.network/natgateways\": { \"SingularDisplayName\": \"NAT gateway\" }\n ,\"microsoft.network/networkexperimentprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profile\" }\n ,\"microsoft.network/networkexperimentprofiles/experiments\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profiles experiment\" }\n ,\"microsoft.network/networkinterfaces\": { \"SingularDisplayName\": \"Network interface\" }\n ,\"microsoft.network/networkmanagerconnections\": { \"SingularDisplayName\": \"Microsoft.Network network manager connection\" }\n ,\"microsoft.network/networkmanagers\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/connectivityconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/ipampools\": { \"SingularDisplayName\": \"IP address pool\" }\n ,\"microsoft.network/networkmanagers/networkgroups\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/routingconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/securityadminconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/securityuserconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/verifierworkspaces\": { \"SingularDisplayName\": \"Verifier Workspace\" }\n ,\"microsoft.network/networkprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network profile\" }\n ,\"microsoft.network/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group\" }\n ,\"microsoft.network/networksecurityperimeters\": { \"SingularDisplayName\": \"Network Security Perimeter\" }\n ,\"microsoft.network/networksecurityperimeters/profiles\": { \"SingularDisplayName\": \"Network Security Perimeter Profile\" }\n ,\"microsoft.network/networkverifiers\": { \"SingularDisplayName\": \"Virtual Network Verifier\" }\n ,\"microsoft.network/networkvirtualappliances\": { \"SingularDisplayName\": \"Microsoft.Network network virtual appliance\" }\n ,\"microsoft.network/networkwatchers\": { \"SingularDisplayName\": \"Network Watcher\" }\n ,\"microsoft.network/networkwatchers/flowlogs\": { \"SingularDisplayName\": \"Flow log\" }\n ,\"microsoft.network/p2svpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Point to Site)\" }\n ,\"microsoft.network/privatednszones\": { \"SingularDisplayName\": \"Private DNS zone\" }\n ,\"microsoft.network/privatednszones/virtualnetworklinks\": { \"SingularDisplayName\": \"Virtual network link\" }\n ,\"microsoft.network/privateendpoints\": { \"SingularDisplayName\": \"Private endpoint\" }\n ,\"microsoft.network/privatelinkservices\": { \"SingularDisplayName\": \"Private link service\" }\n ,\"microsoft.network/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\n ,\"microsoft.network/publicipprefixes\": { \"SingularDisplayName\": \"Public IP Prefix\" }\n ,\"microsoft.network/routefilters\": { \"SingularDisplayName\": \"Route filter\" }\n ,\"microsoft.network/routetables\": { \"SingularDisplayName\": \"Route table\" }\n ,\"microsoft.network/securitypartnerproviders\": { \"SingularDisplayName\": \"Microsoft.Network security partner provider\" }\n ,\"microsoft.network/serviceendpointpolicies\": { \"SingularDisplayName\": \"Service endpoint policy\" }\n ,\"microsoft.network/trafficmanagergeographichierarchies\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager geographic hierarchy\" }\n ,\"microsoft.network/trafficmanagerprofiles\": { \"SingularDisplayName\": \"Traffic Manager profile\" }\n ,\"microsoft.network/trafficmanagerusermetricskeys\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager user metrics key\" }\n ,\"microsoft.network/virtualhubs\": { \"SingularDisplayName\": \"Microsoft.Network/virtualHub\" }\n ,\"microsoft.network/virtualnetworkgateways\": { \"SingularDisplayName\": \"Virtual network gateway\" }\n ,\"microsoft.network/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network\" }\n ,\"microsoft.network/virtualnetworktaps\": { \"SingularDisplayName\": \"Virtual network terminal access point\" }\n ,\"microsoft.network/virtualrouters\": { \"SingularDisplayName\": \"Microsoft.Network virtual router\" }\n ,\"microsoft.network/virtualrouters/peerings\": { \"SingularDisplayName\": \"Microsoft.Network virtual routers peering\" }\n ,\"microsoft.network/virtualwans\": { \"SingularDisplayName\": \"Virtual WAN\" }\n ,\"microsoft.network/vpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Site to Site)\" }\n ,\"microsoft.network/vpngateways/vpnconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connection\" }\n ,\"microsoft.network/vpngateways/vpnconnections/vpnlinkconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connections VPN link connection\" }\n ,\"microsoft.network/vpnserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network VPN server configuration\" }\n ,\"microsoft.network/vpnsites\": { \"SingularDisplayName\": \"Microsoft.Network VPN site\" }\n ,\"microsoft.network/vpnsites/vpnsitelinks\": { \"SingularDisplayName\": \"Microsoft.Network VPN sites VPN site link\" }\n ,\"microsoft.networkanalytics/dataconnectors\": { \"SingularDisplayName\": \"AIOps - Data Connector\" }\n ,\"microsoft.networkanalytics/datalakehouses\": { \"SingularDisplayName\": \"AIOps - Data LakeHouse\" }\n ,\"microsoft.networkanalytics/dataproducts\": { \"SingularDisplayName\": \"Azure Operator Insights ? Data Product\" }\n ,\"microsoft.networkanalytics/dataproducts/datatypes\": { \"SingularDisplayName\": \"Data Type\" }\n ,\"microsoft.networkanalytics/dataproductscatalogs\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics data products catalog\" }\n ,\"microsoft.networkanalytics/metricsingestionendpoints\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics metrics ingestion endpoint\" }\n ,\"microsoft.networkanalytics/networkanalyticsproducts\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics network analytics product\" }\n ,\"microsoft.networkcloud/baremetalmachines\": { \"SingularDisplayName\": \"Bare Metal Machine (Operator Nexus)\" }\n ,\"microsoft.networkcloud/cloudservicesnetworks\": { \"SingularDisplayName\": \"Cloud Services Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clustermanagers\": { \"SingularDisplayName\": \"Cluster Manager (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters\": { \"SingularDisplayName\": \"Cluster (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/baremetalmachinekeysets\": { \"SingularDisplayName\": \"Cluster Bare Metal Machine Key Set (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/bmckeysets\": { \"SingularDisplayName\": \"Cluster Baseboard Management Controller Key Set (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/metricsconfigurations\": { \"SingularDisplayName\": \"Cluster Metrics Configuration (Operator Nexus)\" }\n ,\"microsoft.networkcloud/edgeclustermachineskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster machine SKU\" }\n ,\"microsoft.networkcloud/edgeclusterruntimeversions\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster runtime version\" }\n ,\"microsoft.networkcloud/edgeclusters\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster\" }\n ,\"microsoft.networkcloud/edgeclusters/nodes\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge clusters node\" }\n ,\"microsoft.networkcloud/edgeclusterskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster SKU\" }\n ,\"microsoft.networkcloud/kubernetesclusters\": { \"SingularDisplayName\": \"Kubernetes Cluster (Operator Nexus)\" }\n ,\"microsoft.networkcloud/kubernetesclusters/agentpools\": { \"SingularDisplayName\": \"Agent Pool (Operator Nexus)\" }\n ,\"microsoft.networkcloud/kubernetesclusters/features\": { \"SingularDisplayName\": \"Kubernetes Cluster Feature (Operator Nexus)\" }\n ,\"microsoft.networkcloud/l2networks\": { \"SingularDisplayName\": \"Layer 2 Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/l3networks\": { \"SingularDisplayName\": \"Layer 3 Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/racks\": { \"SingularDisplayName\": \"Compute Rack (Operator Nexus)\" }\n ,\"microsoft.networkcloud/rackskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud rack SKU\" }\n ,\"microsoft.networkcloud/registrationhubs\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hub\" }\n ,\"microsoft.networkcloud/registrationhubs/images\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs image\" }\n ,\"microsoft.networkcloud/registrationhubs/machines\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs machine\" }\n ,\"microsoft.networkcloud/storageappliances\": { \"SingularDisplayName\": \"Storage Appliance (Operator Nexus)\" }\n ,\"microsoft.networkcloud/trunkednetworks\": { \"SingularDisplayName\": \"Trunked Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/virtualmachines\": { \"SingularDisplayName\": \"Virtual Machine (Operator Nexus)\" }\n ,\"microsoft.networkcloud/virtualmachines/consoles\": { \"SingularDisplayName\": \"Virtual Machine Console (Operator Nexus)\" }\n ,\"microsoft.networkcloud/volumes\": { \"SingularDisplayName\": \"Volume (Operator Nexus)\" }\n ,\"microsoft.networkfunction/azuretrafficcollectors\": { \"SingularDisplayName\": \"ExpressRoute traffic collector\" }\n ,\"microsoft.networkfunction/meshvpns\": { \"SingularDisplayName\": \"Mesh VPN\" }\n ,\"microsoft.nexusidentity/identitycontrollers\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity controller\" }\n ,\"microsoft.nexusidentity/identitysets\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity set\" }\n ,\"microsoft.notebooks/notebookproxies\": { \"SingularDisplayName\": \"Microsoft.Notebooks notebook proxy\" }\n ,\"microsoft.notificationhubs/namespaces\": { \"SingularDisplayName\": \"Notification Hub Namespace\" }\n ,\"microsoft.notificationhubs/namespaces/notificationhubs\": { \"SingularDisplayName\": \"Notification Hub\" }\n ,\"microsoft.objectstore/osnamespaces\": { \"SingularDisplayName\": \"Microsoft.ObjectStore os namespace\" }\n })[tolower(id)]\n}\n", + "$fxv#3": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_4(id: string) {\n dynamic({\n \"microsoft.offazure/hypervsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv site\" }\n ,\"microsoft.offazure/hypervsites/clusters\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites cluster\" }\n ,\"microsoft.offazure/hypervsites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites host\" }\n ,\"microsoft.offazure/hypervsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites job\" }\n ,\"microsoft.offazure/hypervsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machine\" }\n ,\"microsoft.offazure/hypervsites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machines software inventory\" }\n ,\"microsoft.offazure/hypervsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites operations statu\" }\n ,\"microsoft.offazure/hypervsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites run as account\" }\n ,\"microsoft.offazure/importsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure import site\" }\n ,\"microsoft.offazure/importsites/deletejobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites delete job\" }\n ,\"microsoft.offazure/importsites/exportjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites export job\" }\n ,\"microsoft.offazure/importsites/importjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites import job\" }\n ,\"microsoft.offazure/importsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites job\" }\n ,\"microsoft.offazure/importsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites machine\" }\n ,\"microsoft.offazure/mastersites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master site\" }\n ,\"microsoft.offazure/mastersites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites operations statu\" }\n ,\"microsoft.offazure/mastersites/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private endpoint connection\" }\n ,\"microsoft.offazure/mastersites/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private link resource\" }\n ,\"microsoft.offazure/mastersites/sqlsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql site\" }\n ,\"microsoft.offazure/mastersites/sqlsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites discovery site data source\" }\n ,\"microsoft.offazure/mastersites/sqlsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites job\" }\n ,\"microsoft.offazure/mastersites/sqlsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites operations statu\" }\n ,\"microsoft.offazure/mastersites/sqlsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites run as account\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqlavailabilitygroups\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql availability group\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqldatabases\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql database\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqlservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql server\" }\n ,\"microsoft.offazure/mastersites/webappsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app site\" }\n ,\"microsoft.offazure/mastersites/webappsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites discovery site data source\" }\n ,\"microsoft.offazure/mastersites/webappsites/extendedmachines\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites extended machine\" }\n ,\"microsoft.offazure/mastersites/webappsites/iiswebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web application\" }\n ,\"microsoft.offazure/mastersites/webappsites/iiswebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web server\" }\n ,\"microsoft.offazure/mastersites/webappsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites runasaccount\" }\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web application\" }\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web server\" }\n ,\"microsoft.offazure/serversites\": { \"SingularDisplayName\": \"Microsoft.OffAzure server site\" }\n ,\"microsoft.offazure/serversites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites job\" }\n ,\"microsoft.offazure/serversites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machine\" }\n ,\"microsoft.offazure/serversites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machines software inventory\" }\n ,\"microsoft.offazure/serversites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites operations statu\" }\n ,\"microsoft.offazure/serversites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites run as account\" }\n ,\"microsoft.offazure/vmwaresites\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware site\" }\n ,\"microsoft.offazure/vmwaresites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites host\" }\n ,\"microsoft.offazure/vmwaresites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites job\" }\n ,\"microsoft.offazure/vmwaresites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machine\" }\n ,\"microsoft.offazure/vmwaresites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machines software inventory\" }\n ,\"microsoft.offazure/vmwaresites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites operations statu\" }\n ,\"microsoft.offazure/vmwaresites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites run as account\" }\n ,\"microsoft.offazure/vmwaresites/vcenters\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites vcenter\" }\n ,\"microsoft.offazurespringboot/springbootsites\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsite\" }\n ,\"microsoft.offazurespringboot/springbootsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites error summary\" }\n ,\"microsoft.offazurespringboot/springbootsites/springbootapps\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootapp\" }\n ,\"microsoft.offazurespringboot/springbootsites/springbootservers\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootserver\" }\n ,\"microsoft.offazurespringboot/springbootsites/summaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites summary\" }\n ,\"microsoft.onlineexperimentation/workspaces\": { \"SingularDisplayName\": \"Online Experimentation Workspace\" }\n ,\"microsoft.openenergyplatform/energyservices\": { \"SingularDisplayName\": \"Azure Data Manager for Energy\" }\n ,\"microsoft.openlogisticsplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspace\" }\n ,\"microsoft.openlogisticsplatform/workspaces/applicationregistrations\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application registration\" }\n ,\"microsoft.openlogisticsplatform/workspaces/applications\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application\" }\n ,\"microsoft.openlogisticsplatform/workspaces/eventgridfilters\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces event grid filter\" }\n ,\"microsoft.openlogisticsplatform/workspaces/shares\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share\" }\n ,\"microsoft.openlogisticsplatform/workspaces/sharesubscriptions\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share subscription\" }\n ,\"microsoft.operationalinsights/clusters\": { \"SingularDisplayName\": \"Log Analytics dedicated cluster\" }\n ,\"microsoft.operationalinsights/querypacks\": { \"SingularDisplayName\": \"Log Analytics query pack\" }\n ,\"microsoft.operationalinsights/workspaces\": { \"SingularDisplayName\": \"Log Analytics workspace\" }\n ,\"microsoft.operationsmanagement/managementassociations\": { \"SingularDisplayName\": \"Microsoft.OperationsManagement management association\" }\n ,\"microsoft.operationsmanagement/solutions\": { \"SingularDisplayName\": \"Solution\" }\n ,\"microsoft.operatorvoicemail/operatorvoicemailinstances\": { \"SingularDisplayName\": \"Microsoft.OperatorVoicemail operator voicemail instance\" }\n ,\"microsoft.oraclediscovery/oraclesites\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle site\" }\n ,\"microsoft.oraclediscovery/oraclesites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites error summary\" }\n ,\"microsoft.oraclediscovery/oraclesites/oracledatabases\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle database\" }\n ,\"microsoft.oraclediscovery/oraclesites/oracleservers\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle server\" }\n ,\"microsoft.oraclediscovery/oraclesites/summaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites summary\" }\n ,\"microsoft.orbital/cloudaccessrouters\": { \"SingularDisplayName\": \"Cloud Access Router\" }\n ,\"microsoft.orbital/contactprofiles\": { \"SingularDisplayName\": \"Contact Profile\" }\n ,\"microsoft.orbital/edgesites\": { \"SingularDisplayName\": \"Edge Site\" }\n ,\"microsoft.orbital/geocatalogs\": { \"SingularDisplayName\": \"GeoCatalog\" }\n ,\"microsoft.orbital/globalcommunicationssites\": { \"SingularDisplayName\": \"Microsoft.Orbital global communications site\" }\n ,\"microsoft.orbital/groundstations\": { \"SingularDisplayName\": \"Ground Station\" }\n ,\"microsoft.orbital/l2connections\": { \"SingularDisplayName\": \"L2 Connection\" }\n ,\"microsoft.orbital/sdwancontrollers\": { \"SingularDisplayName\": \"SDWAN Controller\" }\n ,\"microsoft.orbital/spacecrafts\": { \"SingularDisplayName\": \"Spacecraft\" }\n ,\"microsoft.orbital/spacecrafts/contacts\": { \"SingularDisplayName\": \"Contact\" }\n ,\"microsoft.orbital/terminals\": { \"SingularDisplayName\": \"Cloud Access Terminal\" }\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrence\" }\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences/operationresult\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrences operation result\" }\n ,\"microsoft.peering/peerasns\": { \"SingularDisplayName\": \"Microsoft.Peering peer asn\" }\n ,\"microsoft.peering/peerings\": { \"SingularDisplayName\": \"Peering\" }\n ,\"microsoft.peering/peerings/registeredasns\": { \"SingularDisplayName\": \"Registered ASN\" }\n ,\"microsoft.peering/peerings/registeredprefixes\": { \"SingularDisplayName\": \"Registered prefix\" }\n ,\"microsoft.peering/peeringservices\": { \"SingularDisplayName\": \"Peering Service\" }\n ,\"microsoft.peering/peeringservices/prefixes\": { \"SingularDisplayName\": \"Peering Service Prefix\" }\n ,\"microsoft.pki/pkis\": { \"SingularDisplayName\": \"Microsoft.Pki PKI\" }\n ,\"microsoft.pki/pkis/certificateauthorities\": { \"SingularDisplayName\": \"Microsoft.Pki pkis certificate authority\" }\n ,\"microsoft.pki/pkis/enrollmentpolicies\": { \"SingularDisplayName\": \"Microsoft.Pki pkis enrollment policy\" }\n ,\"microsoft.policyinsights/attestations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights attestation\" }\n ,\"microsoft.policyinsights/policymetadata\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights policy metadata\" }\n ,\"microsoft.policyinsights/remediations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights remediation\" }\n ,\"microsoft.portal/consoles\": { \"SingularDisplayName\": \"Microsoft.Portal console\" }\n ,\"microsoft.portal/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\n ,\"microsoft.portal/tenantconfigurations\": { \"SingularDisplayName\": \"Microsoft.Portal tenant configuration\" }\n ,\"microsoft.portal/usersettings\": { \"SingularDisplayName\": \"Microsoft.Portal user setting\" }\n ,\"microsoft.portal/virtual-privatedashboards\": { \"SingularDisplayName\": \"Private dashboard\" }\n ,\"microsoft.portalservices/copilotsettings\": { \"SingularDisplayName\": \"Microsoft.PortalServices copilot setting\" }\n ,\"microsoft.portalservices/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\n ,\"microsoft.portalservices/extensions\": { \"SingularDisplayName\": \"Portal Extension\" }\n ,\"microsoft.portalservices/extensions/deployments\": { \"SingularDisplayName\": \"Extension Deployment\" }\n ,\"microsoft.portalservices/extensions/slots\": { \"SingularDisplayName\": \"Extension Slot\" }\n ,\"microsoft.portalservices/extensions/versions\": { \"SingularDisplayName\": \"Extension Version\" }\n ,\"microsoft.portalservices/settings\": { \"SingularDisplayName\": \"Microsoft.PortalServices setting\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private endpoint connection\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private link resource\" }\n ,\"microsoft.powerbi/workspacecollections\": { \"SingularDisplayName\": \"Microsoft.PowerBI workspace collection\" }\n ,\"microsoft.powerbidedicated/autoscalevcores\": { \"SingularDisplayName\": \"Microsoft.PowerBIDedicated auto scale vcore\" }\n ,\"microsoft.powerbidedicated/capacities\": { \"SingularDisplayName\": \"Power BI Embedded\" }\n ,\"microsoft.powerplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.PowerPlatform account\" }\n ,\"microsoft.premonition/libraries\": { \"SingularDisplayName\": \"Microsoft.Premonition library\" }\n ,\"microsoft.premonition/libraries/analyses\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries analyse\" }\n ,\"microsoft.premonition/libraries/samples\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries sample\" }\n ,\"microsoft.professionalservice/resources\": { \"SingularDisplayName\": \"Professional Service\" }\n ,\"microsoft.programmableconnectivity/gateways\": { \"SingularDisplayName\": \"APC Gateway\" }\n ,\"microsoft.programmableconnectivity/operatorapiconnections\": { \"SingularDisplayName\": \"APC Operator API Connection\" }\n ,\"microsoft.programmableconnectivity/operatorapiplans\": { \"SingularDisplayName\": \"APC Operator API Plan\" }\n ,\"microsoft.proposal/proposals\": { \"SingularDisplayName\": \"Microsoft.Proposal proposal\" }\n ,\"microsoft.providerhub/providerregistrations\": { \"SingularDisplayName\": \"Resource Provider as a Service\" }\n ,\"microsoft.providerhub/providerregistrations/customrollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.providerhub/providerregistrations/defaultrollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\n ,\"microsoft.providerhubdevtest/regionalstresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest regional stresstest\" }\n ,\"microsoft.providerhubdevtest/stresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest stresstest\" }\n ,\"microsoft.purview/accounts\": { \"SingularDisplayName\": \"Microsoft Purview account\" }\n ,\"microsoft.quantum/provideraccounts\": { \"SingularDisplayName\": \"Microsoft.Quantum provider account\" }\n ,\"microsoft.quantum/workspaces\": { \"SingularDisplayName\": \"Quantum Workspace\" }\n ,\"microsoft.quota/groupquotas\": { \"SingularDisplayName\": \"Microsoft.Quota group quota\" }\n ,\"microsoft.quota/groupquotas/groupquotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas group quota request\" }\n ,\"microsoft.quota/groupquotas/quotaallocationrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation request\" }\n ,\"microsoft.quota/groupquotas/quotaallocations\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation\" }\n ,\"microsoft.quota/groupquotas/subscriptionrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription request\" }\n ,\"microsoft.quota/groupquotas/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription\" }\n ,\"microsoft.quota/quotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota quota request\" }\n ,\"microsoft.quota/quotas\": { \"SingularDisplayName\": \"Microsoft.Quota quota\" }\n ,\"microsoft.quota/usages\": { \"SingularDisplayName\": \"Microsoft.Quota usage\" }\n ,\"microsoft.recommendationsservice/accounts\": { \"SingularDisplayName\": \"Intelligent Recommendations Account\" }\n ,\"microsoft.recommendationsservice/accounts/modeling\": { \"SingularDisplayName\": \"Modeling\" }\n ,\"microsoft.recommendationsservice/accounts/serviceendpoints\": { \"SingularDisplayName\": \"Service Endpoint\" }\n ,\"microsoft.recoveryservices/replicationeligibilityresults\": { \"SingularDisplayName\": \"Microsoft.RecoveryServices replication eligibility result\" }\n ,\"microsoft.recoveryservices/vaults\": { \"SingularDisplayName\": \"Recovery Services vault\" }\n ,\"microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems\": { \"SingularDisplayName\": \"Backup Item\" }\n ,\"microsoft.recoveryservicesbvtd/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD\" }\n ,\"microsoft.recoveryservicesbvtd2/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD2\" }\n ,\"microsoft.recoveryservicesintd/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD\" }\n ,\"microsoft.recoveryservicesintd2/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD2\" }\n ,\"microsoft.redhatopenshift/openshiftclusters\": { \"SingularDisplayName\": \"Azure Red Hat OpenShift cluster\" }\n ,\"microsoft.relationships/dependencyof\": { \"SingularDisplayName\": \"Dependency Relationship\" }\n ,\"microsoft.relationships/servicegroupmember\": { \"SingularDisplayName\": \"Service group member relationship\" }\n ,\"microsoft.relationships/servicegrouprelationships\": { \"SingularDisplayName\": \"Connected Resource\" }\n ,\"microsoft.relay/namespaces\": { \"SingularDisplayName\": \"Relay\" }\n ,\"microsoft.relay/namespaces/hybridconnections\": { \"SingularDisplayName\": \"Hybrid connection\" }\n ,\"microsoft.relay/namespaces/wcfrelays\": { \"SingularDisplayName\": \"WCF relay\" }\n ,\"microsoft.resilience/resiliencestates\": { \"SingularDisplayName\": \"Microsoft.Resilience resilience state\" }\n ,\"microsoft.resourceconnector/appliances\": { \"SingularDisplayName\": \"Resource bridge\" }\n ,\"microsoft.resourcegraph/queries\": { \"SingularDisplayName\": \"Resource Graph query\" }\n ,\"microsoft.resourcehealth/availabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth availability statuse\" }\n ,\"microsoft.resourcehealth/childavailabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth child availability statuse\" }\n ,\"microsoft.resourcehealth/emergingissues\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth emerging issue\" }\n ,\"microsoft.resourcehealth/events\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth event\" }\n ,\"microsoft.resourcehealth/events/impactedresources\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth events impacted resource\" }\n ,\"microsoft.resourcehealth/metadata\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth metadata\" }\n ,\"microsoft.resources/builtintemplatespecs\": { \"SingularDisplayName\": \"Built-in template spec\" }\n ,\"microsoft.resources/changes\": { \"SingularDisplayName\": \"Microsoft.Resources change\" }\n ,\"microsoft.resources/databoundaries\": { \"SingularDisplayName\": \"Microsoft.Resources data boundary\" }\n ,\"microsoft.resources/deletedresources\": { \"SingularDisplayName\": \"Recycle Bin\" }\n ,\"microsoft.resources/deployments\": { \"SingularDisplayName\": \"Microsoft.Resources deployment\" }\n ,\"microsoft.resources/deployments/operations\": { \"SingularDisplayName\": \"Microsoft.Resources deployments operation\" }\n ,\"microsoft.resources/deploymentscripts\": { \"SingularDisplayName\": \"Deployment Script\" }\n ,\"microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\n ,\"microsoft.resources/mobobrokers\": { \"SingularDisplayName\": \"Microsoft.Resources mobo broker\" }\n ,\"microsoft.resources/resourcechange\": { \"SingularDisplayName\": \"Change Analysis\" }\n ,\"microsoft.resources/resourcechanges\": { \"SingularDisplayName\": \"Resource change\" }\n ,\"microsoft.resources/resourcegraphvisualizer\": { \"SingularDisplayName\": \"Resource Graph Visualizer\" }\n ,\"microsoft.resources/resourcegroups\": { \"SingularDisplayName\": \"Microsoft.Resources resource group\" }\n ,\"microsoft.resources/resources\": { \"SingularDisplayName\": \"Resource\" }\n ,\"microsoft.resources/snapshots\": { \"SingularDisplayName\": \"Microsoft.Resources snapshot\" }\n ,\"microsoft.resources/subscriptions\": { \"SingularDisplayName\": \"Subscription\" }\n ,\"microsoft.resources/subscriptions/resourcegroups\": { \"SingularDisplayName\": \"Resource group\" }\n ,\"microsoft.resources/tags\": { \"SingularDisplayName\": \"Microsoft.Resources tag\" }\n ,\"microsoft.resources/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\n ,\"microsoft.resources/virtualsubscriptionsforresourcepicker\": { \"SingularDisplayName\": \"Subscription\" }\n ,\"microsoft.saas/applications\": { \"SingularDisplayName\": \"Software as a Service (classic)\" }\n ,\"microsoft.saas/resources\": { \"SingularDisplayName\": \"SaaS\" }\n ,\"microsoft.saas/saasresources\": { \"SingularDisplayName\": \"SaaS (classic)\" }\n ,\"microsoft.saashub/cloudservices\": { \"SingularDisplayName\": \"Microsoft.SaaSHub cloud service\" }\n ,\"microsoft.saashub/cloudservices/hidden\": { \"SingularDisplayName\": \"Microsoft SaaS\" }\n ,\"microsoft.saashub/saasresources\": { \"SingularDisplayName\": \"Microsoft.SaaSHub saas resource\" }\n ,\"microsoft.salescopilot/conversationintelligencerecordingaccounts\": { \"SingularDisplayName\": \"Microsoft.SalesCopilot conversation intelligence recording account\" }\n ,\"microsoft.scheduler/jobcollections\": { \"SingularDisplayName\": \"Scheduler job collection\" }\n ,\"microsoft.scheduler/jobcollections/jobs\": { \"SingularDisplayName\": \"Scheduler job\" }\n ,\"microsoft.scom/managedinstances\": { \"SingularDisplayName\": \"SCOM managed instance\" }\n ,\"microsoft.scvmm/availabilitysets\": { \"SingularDisplayName\": \"Microsoft.ScVmm availability set\" }\n ,\"microsoft.scvmm/clouds\": { \"SingularDisplayName\": \"Microsoft.ScVmm cloud\" }\n ,\"microsoft.scvmm/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instance\" }\n ,\"microsoft.scvmm/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances guest agent\" }\n ,\"microsoft.scvmm/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.scvmm/virtualmachines\": { \"SingularDisplayName\": \"SCVMM virtual machine - Azure Arc\" }\n ,\"microsoft.scvmm/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine template\" }\n ,\"microsoft.scvmm/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual network\" }\n ,\"microsoft.scvmm/vmmservers\": { \"SingularDisplayName\": \"SCVMM management server\" }\n ,\"microsoft.search/searchservices\": { \"SingularDisplayName\": \"Search service\" }\n ,\"microsoft.secretmanagementsampleprovider/forecasts\": { \"SingularDisplayName\": \"Microsoft.SecretManagementSampleProvider forecast\" }\n ,\"microsoft.secretsynccontroller/azurekeyvaultsecretproviderclasses\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController Azure key vault secret provider class\" }\n ,\"microsoft.secretsynccontroller/secretsyncs\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController secret sync\" }\n ,\"microsoft.security/adaptivenetworkhardenings\": { \"SingularDisplayName\": \"Microsoft.Security adaptive network hardening\" }\n ,\"microsoft.security/advancedthreatprotectionsettings\": { \"SingularDisplayName\": \"Microsoft.Security advanced threat protection setting\" }\n ,\"microsoft.security/alertssuppressionrules\": { \"SingularDisplayName\": \"Microsoft.Security alerts suppression rule\" }\n ,\"microsoft.security/apicollections\": { \"SingularDisplayName\": \"Microsoft.Security API collection\" }\n ,\"microsoft.security/applications\": { \"SingularDisplayName\": \"Microsoft.Security application\" }\n ,\"microsoft.security/assessmentmetadata\": { \"SingularDisplayName\": \"Microsoft.Security assessment metadata\" }\n ,\"microsoft.security/assessments\": { \"SingularDisplayName\": \"Microsoft.Security assessment\" }\n ,\"microsoft.security/assessments/governanceassignments\": { \"SingularDisplayName\": \"Microsoft.Security assessments governance assignment\" }\n ,\"microsoft.security/assessments/subassessments\": { \"SingularDisplayName\": \"Microsoft.Security assessments sub assessment\" }\n ,\"microsoft.security/assignments\": { \"SingularDisplayName\": \"Microsoft.Security assignment\" }\n ,\"microsoft.security/automations\": { \"SingularDisplayName\": \"Microsoft.Security automation\" }\n ,\"microsoft.security/autoprovisioningsettings\": { \"SingularDisplayName\": \"Microsoft.Security auto provisioning setting\" }\n ,\"microsoft.security/complianceresults\": { \"SingularDisplayName\": \"Microsoft.Security compliance result\" }\n ,\"microsoft.security/compliances\": { \"SingularDisplayName\": \"Microsoft.Security compliance\" }\n ,\"microsoft.security/connectors\": { \"SingularDisplayName\": \"Microsoft.Security connector\" }\n ,\"microsoft.security/customassessmentautomations\": { \"SingularDisplayName\": \"Microsoft.Security custom assessment automation\" }\n ,\"microsoft.security/defenderforstoragesettings\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage setting\" }\n ,\"microsoft.security/defenderforstoragesettings/malwarescans\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage settings malware scan\" }\n ,\"microsoft.security/devicesecuritygroups\": { \"SingularDisplayName\": \"Microsoft.Security device security group\" }\n ,\"microsoft.security/governancerules\": { \"SingularDisplayName\": \"Microsoft.Security governance rule\" }\n ,\"microsoft.security/governancerules/operationresults\": { \"SingularDisplayName\": \"Microsoft.Security governance rules operation result\" }\n ,\"microsoft.security/healthreports\": { \"SingularDisplayName\": \"Microsoft.Security health report\" }\n ,\"microsoft.security/informationprotectionpolicies\": { \"SingularDisplayName\": \"Microsoft.Security information protection policy\" }\n ,\"microsoft.security/iotsecuritysolutions\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solution\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics model\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated alert\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated recommendation\" }\n ,\"microsoft.security/iotsecuritysolutions/iotalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert\" }\n ,\"microsoft.security/iotsecuritysolutions/iotalerttypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert type\" }\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation\" }\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendationtypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation type\" }\n ,\"microsoft.security/locations/alerts\": { \"SingularDisplayName\": \"Security Alert\" }\n ,\"microsoft.security/mdeonboardings\": { \"SingularDisplayName\": \"Microsoft.Security mde onboarding\" }\n ,\"microsoft.security/pricings\": { \"SingularDisplayName\": \"Defender for Cloud\" }\n ,\"microsoft.security/pricings/securityoperators\": { \"SingularDisplayName\": \"Microsoft.Security pricings security operator\" }\n ,\"microsoft.security/regulatorycompliancestandards\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standard\" }\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance control\" }\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols/regulatorycomplianceassessments\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance controls regulatory compliance assessment\" }\n ,\"microsoft.security/securescores\": { \"SingularDisplayName\": \"Microsoft.Security secure score\" }\n ,\"microsoft.security/securityconnectors\": { \"SingularDisplayName\": \"Microsoft.Security security connector\" }\n ,\"microsoft.security/securityconnectors/devops\": { \"SingularDisplayName\": \"Microsoft.Security security connectors devop\" }\n ,\"microsoft.security/securitycontacts\": { \"SingularDisplayName\": \"Microsoft.Security security contact\" }\n ,\"microsoft.security/sensitivitysettings\": { \"SingularDisplayName\": \"Microsoft.Security sensitivity setting\" }\n ,\"microsoft.security/servervulnerabilityassessments\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessment\" }\n ,\"microsoft.security/servervulnerabilityassessmentssettings\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessments setting\" }\n ,\"microsoft.security/settings\": { \"SingularDisplayName\": \"Microsoft.Security setting\" }\n ,\"microsoft.security/standards\": { \"SingularDisplayName\": \"Microsoft.Security standard\" }\n ,\"microsoft.security/workspacesettings\": { \"SingularDisplayName\": \"Microsoft.Security workspace setting\" }\n ,\"microsoft.securitycopilot/capacities\": { \"SingularDisplayName\": \"Microsoft Security compute capacity\" }\n ,\"microsoft.securitydetonation/chambers\": { \"SingularDisplayName\": \"Security Detonation Chamber\" }\n ,\"microsoft.securityinsightsarg/sentinel\": { \"SingularDisplayName\": \"Microsoft Sentinel\" }\n ,\"microsoft.sentinelplatformservices/sentinelplatformservices\": { \"SingularDisplayName\": \"Microsoft.SentinelPlatformServices sentinel platform service\" }\n ,\"microsoft.serialconsole/consoleservices\": { \"SingularDisplayName\": \"Microsoft.SerialConsole console service\" }\n ,\"microsoft.serialconsole/serialports\": { \"SingularDisplayName\": \"Microsoft.SerialConsole serial port\" }\n ,\"microsoft.servicebus/namespaces\": { \"SingularDisplayName\": \"Service Bus namespace\" }\n ,\"microsoft.servicebus/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Service Bus Geo-DR Alias\" }\n ,\"microsoft.servicebus/namespaces/queues\": { \"SingularDisplayName\": \"Service Bus queue\" }\n ,\"microsoft.servicebus/namespaces/topics\": { \"SingularDisplayName\": \"Service Bus topic\" }\n ,\"microsoft.servicebus/namespaces/topics/subscriptions\": { \"SingularDisplayName\": \"Service Bus Subscription\" }\n ,\"microsoft.servicefabric/clusters\": { \"SingularDisplayName\": \"Service Fabric cluster\" }\n ,\"microsoft.servicefabric/managedclusters\": { \"SingularDisplayName\": \"Service Fabric managed cluster\" }\n ,\"microsoft.servicefabricmesh/applications\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh application\" }\n ,\"microsoft.servicefabricmesh/applications/services\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications service\" }\n ,\"microsoft.servicefabricmesh/applications/services/replicas\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications services replica\" }\n ,\"microsoft.servicefabricmesh/gateways\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh gateway\" }\n ,\"microsoft.servicefabricmesh/networks\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh network\" }\n ,\"microsoft.servicefabricmesh/secrets\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secret\" }\n ,\"microsoft.servicefabricmesh/secrets/values\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secrets value\" }\n ,\"microsoft.servicefabricmesh/volumes\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh volume\" }\n ,\"microsoft.servicelinker/dryruns\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker dryrun\" }\n ,\"microsoft.servicelinker/linkers\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker linker\" }\n ,\"microsoft.servicenetworking/trafficcontrollers\": { \"SingularDisplayName\": \"Application Gateway for Containers\" }\n ,\"microsoft.serviceshub/connectors\": { \"SingularDisplayName\": \"Services Hub Connector\" }\n ,\"microsoft.signalrservice/signalr\": { \"SingularDisplayName\": \"SignalR\" }\n ,\"microsoft.signalrservice/signalr/replicas\": { \"SingularDisplayName\": \"SignalR Replica\" }\n ,\"microsoft.signalrservice/webpubsub\": { \"SingularDisplayName\": \"Web PubSub Service\" }\n ,\"microsoft.signalrservice/webpubsub/replicas\": { \"SingularDisplayName\": \"Web PubSub Service Replica\" }\n ,\"microsoft.skytap/billingnodes\": { \"SingularDisplayName\": \"Microsoft.Skytap billing node\" }\n ,\"microsoft.skytap/interfaces\": { \"SingularDisplayName\": \"Microsoft.Skytap interface\" }\n ,\"microsoft.skytap/nodes\": { \"SingularDisplayName\": \"Microsoft.Skytap node\" }\n ,\"microsoft.softwareplan/hybridusebenefits\": { \"SingularDisplayName\": \"Microsoft.SoftwarePlan hybrid use benefit\" }\n ,\"microsoft.solutions/applicationdefinitions\": { \"SingularDisplayName\": \"Service catalog managed application definition\" }\n ,\"microsoft.solutions/applications\": { \"SingularDisplayName\": \"Managed application\" }\n ,\"microsoft.solutions/jitrequests\": { \"SingularDisplayName\": \"Microsoft.Solutions JIT request\" }\n ,\"microsoft.sovereign/landingzoneaccounts\": { \"SingularDisplayName\": \"Landing zone account\" }\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\n ,\"microsoft.sovereign/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\n ,\"microsoft.sovereign/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\n ,\"microsoft.sovereign/transparencylogs\": { \"SingularDisplayName\": \"Transparency log\" }\n ,\"microsoft.sql/azuresql\": { \"SingularDisplayName\": \"Azure SQL resource\" }\n ,\"microsoft.sql/instancepools\": { \"SingularDisplayName\": \"Instance pool\" }\n ,\"microsoft.sql/managedinstances\": { \"SingularDisplayName\": \"SQL managed instance\" }\n ,\"microsoft.sql/managedinstances/databases\": { \"SingularDisplayName\": \"Managed database\" }\n ,\"microsoft.sql/servers\": { \"SingularDisplayName\": \"SQL server\" }\n ,\"microsoft.sql/servers/databases\": { \"SingularDisplayName\": \"SQL database\" }\n ,\"microsoft.sql/servers/elasticpools\": { \"SingularDisplayName\": \"SQL elastic pool\" }\n ,\"microsoft.sql/servers/jobagents\": { \"SingularDisplayName\": \"Elastic Job agent\" }\n ,\"microsoft.sql/virtualclusters\": { \"SingularDisplayName\": \"Virtual cluster\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine group\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups/availabilitygrouplisteners\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine groups availability group listener\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachines\": { \"SingularDisplayName\": \"SQL virtual machine\" }\n ,\"microsoft.standbypool/standbycontainergrouppools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pool\" }\n ,\"microsoft.standbypool/standbycontainergrouppools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pools runtime view\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pool\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools runtime view\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools/standbyvirtualmachines\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools standby virtual machine\" }\n ,\"microsoft.storage/storageaccounts\": { \"SingularDisplayName\": \"Storage account\" }\n ,\"microsoft.storageactions/storagetasks\": { \"SingularDisplayName\": \"Storage task - Azure Storage Actions\" }\n ,\"microsoft.storagecache/amlfilesystems\": { \"SingularDisplayName\": \"Azure Managed Lustre\" }\n ,\"microsoft.storagecache/caches\": { \"SingularDisplayName\": \"HPC cache\" }\n ,\"microsoft.storagediscovery/storagediscoveryworkspaces\": { \"SingularDisplayName\": \"Storage Discovery workspace\" }\n ,\"microsoft.storagehub/all\": { \"SingularDisplayName\": \"All resources\" }\n ,\"microsoft.storagehub/policycomplianceresources\": { \"SingularDisplayName\": \"Policy compliance\" }\n ,\"microsoft.storageinsights/storagecollectionrules\": { \"SingularDisplayName\": \"Microsoft.StorageInsights storage collection rule\" }\n ,\"microsoft.storagemover/storagemovers\": { \"SingularDisplayName\": \"Storage mover\" }\n ,\"microsoft.storagepool/diskpools\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pool\" }\n ,\"microsoft.storagepool/diskpools/iscsitargets\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pools iscsi target\" }\n ,\"microsoft.storagesync/storagesyncservices\": { \"SingularDisplayName\": \"Storage Sync Service\" }\n ,\"microsoft.storagetasks/storagetasks\": { \"SingularDisplayName\": \"Microsoft.StorageTasks storage task\" }\n ,\"microsoft.storsimple/managers\": { \"SingularDisplayName\": \"StorSimple device manager\" }\n ,\"microsoft.storsimple/managers/accesscontrolrecords\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers access control record\" }\n ,\"microsoft.storsimple/managers/bandwidthsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers bandwidth setting\" }\n ,\"microsoft.storsimple/managers/certificates\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers certificate\" }\n ,\"microsoft.storsimple/managers/devices\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers device\" }\n ,\"microsoft.storsimple/managers/devices/alertsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices alert setting\" }\n ,\"microsoft.storsimple/managers/devices/backuppolicies\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policy\" }\n ,\"microsoft.storsimple/managers/devices/backuppolicies/schedules\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policies schedule\" }\n ,\"microsoft.storsimple/managers/devices/backupschedulegroups\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup schedule group\" }\n ,\"microsoft.storsimple/managers/devices/chapsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices chap setting\" }\n ,\"microsoft.storsimple/managers/devices/fileservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileserver\" }\n ,\"microsoft.storsimple/managers/devices/fileservers/shares\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileservers share\" }\n ,\"microsoft.storsimple/managers/devices/iscsiservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiserver\" }\n ,\"microsoft.storsimple/managers/devices/iscsiservers/disks\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiservers disk\" }\n ,\"microsoft.storsimple/managers/devices/jobs\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices job\" }\n ,\"microsoft.storsimple/managers/devices/networksettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices network setting\" }\n ,\"microsoft.storsimple/managers/devices/securitysettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices security setting\" }\n ,\"microsoft.storsimple/managers/devices/timesettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices time setting\" }\n ,\"microsoft.storsimple/managers/devices/updatesummary\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices update summary\" }\n ,\"microsoft.storsimple/managers/devices/volumecontainers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume container\" }\n ,\"microsoft.storsimple/managers/devices/volumecontainers/volumes\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume containers volume\" }\n ,\"microsoft.storsimple/managers/encryptionsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers encryption setting\" }\n ,\"microsoft.storsimple/managers/extendedinformation\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers extended information\" }\n ,\"microsoft.storsimple/managers/storageaccountcredentials\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage account credential\" }\n ,\"microsoft.storsimple/managers/storagedomains\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage domain\" }\n ,\"microsoft.streamanalytics/clusters\": { \"SingularDisplayName\": \"Stream Analytics cluster\" }\n ,\"microsoft.streamanalytics/streamingjobs\": { \"SingularDisplayName\": \"Stream Analytics job\" }\n ,\"microsoft.subscription/aliases\": { \"SingularDisplayName\": \"Microsoft.Subscription aliase\" }\n ,\"microsoft.subscription/changetenantrequest\": { \"SingularDisplayName\": \"Microsoft.Subscription change tenant request\" }\n ,\"microsoft.subscription/policies\": { \"SingularDisplayName\": \"Microsoft.Subscription policy\" }\n ,\"microsoft.subscription/subscriptiondefinitions\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription definition\" }\n ,\"microsoft.subscription/subscriptionoperations\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription operation\" }\n ,\"microsoft.support/fileworkspaces\": { \"SingularDisplayName\": \"Microsoft.Support file workspace\" }\n ,\"microsoft.support/fileworkspaces/files\": { \"SingularDisplayName\": \"Microsoft.Support file workspaces file\" }\n ,\"microsoft.support/services\": { \"SingularDisplayName\": \"Microsoft.Support service\" }\n ,\"microsoft.support/services/problemclassifications\": { \"SingularDisplayName\": \"Microsoft.Support services problem classification\" }\n ,\"microsoft.support/supporttickets\": { \"SingularDisplayName\": \"Support Request\" }\n ,\"microsoft.sustainabilityservices/calculations\": { \"SingularDisplayName\": \"Project Sustainability Calculator\" }\n ,\"microsoft.symphony/instances\": { \"SingularDisplayName\": \"Microsoft.Symphony instance\" }\n ,\"microsoft.symphony/solutions\": { \"SingularDisplayName\": \"Microsoft.Symphony solution\" }\n ,\"microsoft.symphony/targets\": { \"SingularDisplayName\": \"Microsoft.Symphony target\" }\n ,\"microsoft.synapse/privatelinkhubs\": { \"SingularDisplayName\": \"Synapse private link hub\" }\n ,\"microsoft.synapse/workspaces\": { \"SingularDisplayName\": \"Synapse workspace\" }\n ,\"microsoft.synapse/workspaces/bigdatapools\": { \"SingularDisplayName\": \"Apache Spark pool\" }\n ,\"microsoft.synapse/workspaces/kustopools\": { \"SingularDisplayName\": \"Data Explorer pool\" }\n ,\"microsoft.synapse/workspaces/kustopools/databases\": { \"SingularDisplayName\": \"Data Explorer Database\" }\n ,\"microsoft.synapse/workspaces/scopepools\": { \"SingularDisplayName\": \"SCOPE pool\" }\n ,\"microsoft.synapse/workspaces/sqlpools\": { \"SingularDisplayName\": \"Dedicated SQL pool\" }\n ,\"microsoft.syntex/accounts\": { \"SingularDisplayName\": \"Microsoft.Syntex account\" }\n ,\"microsoft.syntex/documentprocessors\": { \"SingularDisplayName\": \"Microsoft.Syntex document processor\" }\n ,\"microsoft.test/healthdataaiservices\": { \"SingularDisplayName\": \"Azure Health Data and AI Services\" }\n ,\"microsoft.timeseriesinsights/environments\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environment\" }\n ,\"microsoft.timeseriesinsights/environments/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments access policy\" }\n ,\"microsoft.timeseriesinsights/environments/eventsources\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments event source\" }\n ,\"microsoft.timeseriesinsights/environments/referencedatasets\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments reference data set\" }\n ,\"microsoft.toolchainorchestrator/activations\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator activation\" }\n ,\"microsoft.toolchainorchestrator/campaigns\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaign\" }\n ,\"microsoft.toolchainorchestrator/campaigns/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaigns version\" }\n ,\"microsoft.toolchainorchestrator/catalogs\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalog\" }\n ,\"microsoft.toolchainorchestrator/catalogs/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalogs version\" }\n ,\"microsoft.toolchainorchestrator/diagnostics\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator diagnostic\" }\n ,\"microsoft.toolchainorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instance\" }\n ,\"microsoft.toolchainorchestrator/instances/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instances version\" }\n ,\"microsoft.toolchainorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solution\" }\n ,\"microsoft.toolchainorchestrator/solutions/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solutions version\" }\n ,\"microsoft.toolchainorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator target\" }\n ,\"microsoft.toolchainorchestrator/targets/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator targets version\" }\n ,\"microsoft.updatemanager/updaterules\": { \"SingularDisplayName\": \"Update Rule\" }\n ,\"microsoft.usagebilling/accounts\": { \"SingularDisplayName\": \"Microsoft.UsageBilling account\" }\n ,\"microsoft.usagebilling/accounts/dataexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts data export\" }\n ,\"microsoft.usagebilling/accounts/inputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts input\" }\n ,\"microsoft.usagebilling/accounts/metricexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts metric export\" }\n ,\"microsoft.usagebilling/accounts/pav2outputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pav2output\" }\n ,\"microsoft.usagebilling/accounts/pipelines\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipeline\" }\n ,\"microsoft.usagebilling/accounts/pipelines/outputselectors\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipelines output selector\" }\n ,\"microsoft.verifiedid/authorities\": { \"SingularDisplayName\": \"Microsoft.VerifiedId authority\" }\n ,\"microsoft.videoindexer/accounts\": { \"SingularDisplayName\": \"Azure AI Video Indexer\" }\n ,\"microsoft.virtualmachineimages/imagetemplates\": { \"SingularDisplayName\": \"Image template\" }\n ,\"microsoft.visualstudio/account\": { \"SingularDisplayName\": \"Azure DevOps organization\" }\n ,\"microsoft.vmware/resourcepools\": { \"SingularDisplayName\": \"Microsoft.VMware resource pool\" }\n ,\"microsoft.vmware/vcenters\": { \"SingularDisplayName\": \"Microsoft.VMware vcenter\" }\n ,\"microsoft.vmware/vcenters/inventoryitems\": { \"SingularDisplayName\": \"Microsoft.VMware vcenters inventory item\" }\n ,\"microsoft.vmware/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine\" }\n ,\"microsoft.vmware/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine template\" }\n ,\"microsoft.vmware/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.VMware virtual network\" }\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudnodes\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud node\" }\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudservices\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud service\" }\n ,\"microsoft.vmwarecloudsimple/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple virtual machine\" }\n ,\"microsoft.vnfmanager/devices\": { \"SingularDisplayName\": \"Microsoft.VnfManager device\" }\n ,\"microsoft.vnfmanager/vendors\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendor\" }\n ,\"microsoft.vnfmanager/vendors/skus\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendors SKU\" }\n ,\"microsoft.vnfmanager/vnfs\": { \"SingularDisplayName\": \"Microsoft.VnfManager vnf\" }\n ,\"microsoft.voiceservices/communicationsgateways\": { \"SingularDisplayName\": \"Communications Gateway\" }\n ,\"microsoft.voiceservices/communicationsgateways/testlines\": { \"SingularDisplayName\": \"Communications Gateway Test Line\" }\n ,\"microsoft.vsonline/accounts\": { \"SingularDisplayName\": \"Microsoft.VSOnline account\" }\n ,\"microsoft.vsonline/plans\": { \"SingularDisplayName\": \"Visual Studio Online Plan\" }\n ,\"microsoft.web/certificates\": { \"SingularDisplayName\": \"Microsoft.Web certificate\" }\n ,\"microsoft.web/connectiongateways\": { \"SingularDisplayName\": \"App Service on-premises data gateway\" }\n ,\"microsoft.web/connections\": { \"SingularDisplayName\": \"App Service API connection\" }\n ,\"microsoft.web/containerapps\": { \"SingularDisplayName\": \"Microsoft.Web container app\" }\n ,\"microsoft.web/containerapps/revisions\": { \"SingularDisplayName\": \"Microsoft.Web container apps revision\" }\n ,\"microsoft.web/customapis\": { \"SingularDisplayName\": \"Logic apps custom connector\" }\n ,\"microsoft.web/deletedsites\": { \"SingularDisplayName\": \"Microsoft.Web deleted site\" }\n ,\"microsoft.web/hostingenvironments\": { \"SingularDisplayName\": \"App Service Environment\" }\n ,\"microsoft.web/ishostingenvironmentnameavailable\": { \"SingularDisplayName\": \"Microsoft.Web ishostingenvironmentnameavailable\" }\n ,\"microsoft.web/kubeenvironments\": { \"SingularDisplayName\": \"App Service Kubernetes Environment\" }\n ,\"microsoft.web/logicappstemplate\": { \"SingularDisplayName\": \"Logic Apps Template\" }\n ,\"microsoft.web/publishingusers\": { \"SingularDisplayName\": \"Microsoft.Web publishing user\" }\n ,\"microsoft.web/serverfarms\": { \"SingularDisplayName\": \"App Service plan\" }\n ,\"microsoft.web/sites\": { \"SingularDisplayName\": \"App Service web app\" }\n ,\"microsoft.web/sites/slots\": { \"SingularDisplayName\": \"App Service deployment slot\" }\n ,\"microsoft.web/sourcecontrols\": { \"SingularDisplayName\": \"Microsoft.Web sourcecontrol\" }\n ,\"microsoft.web/staticsites\": { \"SingularDisplayName\": \"Static Web App\" }\n ,\"microsoft.weightsandbiases/instances\": { \"SingularDisplayName\": \"Azure Native Weights & Biases Cloud Service\" }\n ,\"microsoft.whiteboxcadlprovider/whiteboxresources\": { \"SingularDisplayName\": \"Microsoft.WhiteBoxCadlProvider white box resource\" }\n ,\"microsoft.windows365/cloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.Windows365 cloud pc delegated msi\" }\n ,\"microsoft.windowsesu/multipleactivationkeys\": { \"SingularDisplayName\": \"Microsoft.WindowsESU multiple activation key\" }\n ,\"microsoft.windowsiot/deviceservices\": { \"SingularDisplayName\": \"Microsoft.WindowsIoT device service\" }\n ,\"microsoft.windowspushnotificationservices/registrations\": { \"SingularDisplayName\": \"Windows Push Notification Service\" }\n ,\"microsoft.workloadmonitor/monitors\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitor\" }\n ,\"microsoft.workloadmonitor/monitors/history\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitors history\" }\n ,\"microsoft.workloads/configurationvalidationresults\": { \"SingularDisplayName\": \"Microsoft.Workloads configuration validation result\" }\n ,\"microsoft.workloads/connectors\": { \"SingularDisplayName\": \"Microsoft.Workloads connector\" }\n ,\"microsoft.workloads/connectors/acssbackups\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors acss backup\" }\n ,\"microsoft.workloads/connectors/amsinsights\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors ams insight\" }\n ,\"microsoft.workloads/connectors/sapvirtualinstancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors sap virtual instance monitor\" }\n ,\"microsoft.workloads/epicvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for Epic solution\" }\n ,\"microsoft.workloads/insights\": { \"SingularDisplayName\": \"Microsoft.Workloads insight\" }\n ,\"microsoft.workloads/instancegroupmonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance group monitor\" }\n ,\"microsoft.workloads/instancehealthdefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definition\" }\n ,\"microsoft.workloads/instancehealthdefinitions/signaldefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definitions signal definition\" }\n ,\"microsoft.workloads/instancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance monitor\" }\n ,\"microsoft.workloads/monitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP solutions\" }\n ,\"microsoft.workloads/oraclevirtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instance\" }\n ,\"microsoft.workloads/oraclevirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instances database instance\" }\n ,\"microsoft.workloads/phpworkloads\": { \"SingularDisplayName\": \"Microsoft.Workloads php workload\" }\n ,\"microsoft.workloads/phpworkloads/wordpressinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads php workloads wordpress instance\" }\n ,\"microsoft.workloads/sapdiscoverysites\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery site\" }\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instance\" }\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances/serverinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instances server instance\" }\n ,\"microsoft.workloads/sapvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/applicationinstances\": { \"SingularDisplayName\": \"App server instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/centralinstances\": { \"SingularDisplayName\": \"Central service instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Database for SAP solutions\" }\n ,\"microsoft.workloads/virtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instance\" }\n ,\"microsoft.workloads/virtualinstances/components\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instances component\" }\n ,\"microsoft.workloads/workloadinstance\": { \"SingularDisplayName\": \"My Resource\" }\n ,\"microsoft.zerotrustsegmentation/segmentationmanagers\": { \"SingularDisplayName\": \"Segmentation Manager\" }\n ,\"mongodb.atlas/organizations\": { \"SingularDisplayName\": \"MongoDB Atlas Organization\" }\n ,\"neon.postgres/organizations\": { \"SingularDisplayName\": \"Neon Serverless Postgres Organization\" }\n ,\"newrelic.observability/monitors\": { \"SingularDisplayName\": \"New Relic\" }\n ,\"nginx.nginxplus/nginxdeployments\": { \"SingularDisplayName\": \"NGINXaaS\" }\n ,\"oracle.database/autonomousdatabases\": { \"SingularDisplayName\": \"Autonomous Database\" }\n ,\"oracle.database/basedb\": { \"SingularDisplayName\": \"Autonomous Database\" }\n ,\"oracle.database/cloudexadatainfrastructures\": { \"SingularDisplayName\": \"Oracle Exadata Infrastructure\" }\n ,\"oracle.database/cloudvmclusters\": { \"SingularDisplayName\": \"Oracle Exadata VM Cluster\" }\n ,\"oracle.database/exadbvmclusters\": { \"SingularDisplayName\": \"Oracle Exascale VM Cluster\" }\n ,\"oracle.database/exascaledbstoragevaults\": { \"SingularDisplayName\": \"Oracle Exascale DB Storage Vault\" }\n ,\"oracle.database/networkanchors\": { \"SingularDisplayName\": \"Network Anchor\" }\n ,\"oracle.database/oraclesubscriptions\": { \"SingularDisplayName\": \"OracleSubscription\" }\n ,\"oracle.database/resourceanchors\": { \"SingularDisplayName\": \"Resource Anchor\" }\n ,\"paloaltonetworks.cloudngfw/firewalls\": { \"SingularDisplayName\": \"Cloud NGFW by Palo Alto Networks\" }\n ,\"paloaltonetworks.cloudngfw/globalrulestacks\": { \"SingularDisplayName\": \"Global Rulestack\" }\n ,\"paloaltonetworks.cloudngfw/localrulestacks\": { \"SingularDisplayName\": \"Local Rulestack for Cloud NGFW by Palo Alto Networks\" }\n ,\"pinecone.vectordb/organizations\": { \"SingularDisplayName\": \"Azure Native Pinecone Cloud Service\" }\n ,\"purestorage.block/reservations\": { \"SingularDisplayName\": \"Azure Native Pure Storage Cloud Service\" }\n ,\"purestorage.block/storagepools\": { \"SingularDisplayName\": \"Storage pool\" }\n ,\"purestorage.block/storagepools/avsstoragecontainers\": { \"SingularDisplayName\": \"PureStorage.Block storage pools avs storage container\" }\n })[tolower(id)]\n}\n", + "$fxv#4": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_5(id: string) {\n dynamic({\n \"qumulo.qaas/storages\": { \"SingularDisplayName\": \"Qumulo.QaaS storage\" }\n ,\"qumulo.storage/filesystems\": { \"SingularDisplayName\": \"Azure Native Qumulo Scalable File Service\" }\n ,\"solarwinds.observability/organizations\": { \"SingularDisplayName\": \"SolarWinds Observability\" }\n ,\"splitio.experimentation/experimentationworkspaces\": { \"SingularDisplayName\": \"Split Experimentation Workspace\" }\n ,\"wandisco.fusion/migrators\": { \"SingularDisplayName\": \"LiveData Migrator\" }\n ,\"wandisco.fusion/migrators/datatransferagents\": { \"SingularDisplayName\": \"Data Transfer Agent\" }\n ,\"wandisco.fusion/migrators/exclusiontemplates\": { \"SingularDisplayName\": \"Exclusion\" }\n ,\"wandisco.fusion/migrators/livedatamigrations\": { \"SingularDisplayName\": \"Migration\" }\n ,\"wandisco.fusion/migrators/metadatamigrations\": { \"SingularDisplayName\": \"Metadata Migration\" }\n ,\"wandisco.fusion/migrators/metadatatargets\": { \"SingularDisplayName\": \"Metadata Target\" }\n ,\"wandisco.fusion/migrators/pathmappings\": { \"SingularDisplayName\": \"Path Mapping\" }\n ,\"wandisco.fusion/migrators/targets\": { \"SingularDisplayName\": \"Target\" }\n ,\"wandisco.fusion/migrators/verifications\": { \"SingularDisplayName\": \"Verification\" }\n })[tolower(id)]\n}\n", + "$fxv#5": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// resource_type\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData')\nresource_type(id: string) {\n coalesce(_resource_type_1(id), _resource_type_2(id), _resource_type_3(id), _resource_type_4(id), _resource_type_5(id))\n}\n", + "$fxv#6": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Common utility functions\n//\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\n//======================================================================================================================\n\n\n//===| Date functions |=================================================================================================\n\n// monthstring\n.create-or-alter function \nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \nmonthstring(['date']: datetime, length: int = 9)\n{\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\n}\n\n// datestring\n.create-or-alter function \nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n let month = (d: datetime) { monthstring(d, 3) };\n let endDate = iff(end == datetime('0001-01-01'), start, end);\n let sameDate = startofday(start) == startofday(endDate);\n let sameMonth = startofmonth(start) == startofmonth(endDate);\n let sameYear = startofyear(start) == startofyear(endDate);\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\n let currentYear = sameYear and startofyear(start) == startofyear(now());\n case(\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\n fullYear,\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\n // 1 full mo, same year | Mmm yyyy\n fullMonth and sameMonth and sameYear,\n strcat(month(start), ' ', getyear(start)),\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\n fullMonth and sameYear,\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\n fullMonth and not(sameYear),\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\n sameDate,\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\n not(fullMonth) and sameMonth and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\n not(fullMonth) and not(sameMonth) and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\n )\n}\n\n// daterange\n.create-or-alter function \nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n datestring(start, end)\n}\n\n// monthsago\n.create-or-alter function \nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\nmonthsago(months: int)\n{\n datetime_add('month', -months, startofmonth(now()))\n}\n\n\n//===| Number functions |===============================================================================================\n// NOTE: Must be defined before string converters\n\n// delta\n.create-or-alter function \nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \ndelta(oldval: double, newval: double)\n{\n (newval - todouble(oldval))/oldval\n}\n\n// percentOfTotal\n// NOTE: Must be before percent() function\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercentOfTotal(t: (Count: long), tot: long)\n{\n let total = todouble(tot);\n t \n | extend Percent = round(Count / total * 100, 3) \n | order by Count desc\n}\n\n// percent\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercent(t: (Count: long))\n{\n let total = todouble(toscalar(t | summarize sum(Count)));\n percentOfTotal(t, total)\n}\n\n// plusminus\n.create-or-alter function \nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\nplusminus(val: string)\n{\n let neg = substring(val, 0, 1) == '-';\n iff(neg, val, strcat('+', val))\n}\n\n// updown\n.create-or-alter function \nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\nupdown(val: string)\n{\n // TODO: Handle 0\n let neg = substring(val, 0, 1) == '-';\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\n}\n\n\n//===| String functions |===============================================================================================\n\n// percentstring\n// NOTE: Must be defined before deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\npercentstring(num: double, total: double = 1.0, places: int = 9)\n{\n let value = 1.0 * num / total * 100;\n strcat(case(\n places != 9, round(value, places),\n value < 10, round(value, 2),\n round(value, 1)\n ), '%')\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// arraystring\n.create-or-alter function \nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\narraystring(arr: dynamic)\n{\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\n tostring(arr)\n , @'^\\[\"', '')\n , @'\"\\]$', '')\n , @'^, ', '')\n , @', $', '')\n , @'^\\[]$', '')\n , '\",\"', ', ')\n}\n\n// deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\n{\n let d = delta(oldval, newval);\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\n}\n\n// diffstring\n.create-or-alter function \nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\ndiffstring(oldval: double, newval: double, places: int = 1)\n{\n plusminus(round(newval - oldval, places))\n}\n\n// numberstring\n.create-or-alter function \nwith (docstring = 'Convert a number to a string', folder = 'Common')\nnumberstring(num: double, abbrev: bool = true)\n{\n replace_regex(case(\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\n tostring(num)\n ), @'\\.0$', '')\n}\n\n\n//===| Other |==========================================================================================================\n\n// ifempty\n.create-or-alter function \nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\nifempty(val: dynamic, defaultVal: dynamic)\n{\n iff(isempty(val), defaultVal, val)\n}\n", + "$fxv#7": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Settings |=======================================================================================================\n\n.create-merge table HubSettingsLog (\n version: string,\n scopes: dynamic,\n retention: dynamic\n)\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// HubSettings function\n.create-or-alter function\nwith (docstring='Gets the latest version of hub settings.', folder='Settings')\nHubSettings()\n{\n HubSettingsLog\n | extend timestamp = ingestion_time()\n | summarize arg_max(timestamp, *)\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// HubScopes function\n.create-or-alter function\nwith (docstring='Gets the currently configured scopes.', folder='Settings')\nHubScopes()\n{\n HubSettings\n | project scopes\n | mv-expand scopes\n}\n\n\n//===| Open data |======================================================================================================\n\n// PricingUnits -- Create table if it doesn't exist\n.create-merge table PricingUnits ( ignore: string )\n\n// PricingUnits -- Remove all columns\n.alter table PricingUnits ( ignore: string )\n\n// PricingUnits -- Redefine all columns to change types\n.alter table PricingUnits (\n x_PricingUnitDescription: string,\n x_PricingBlockSize: real,\n PricingUnit: string\n)\n\n// Regions\n.create-merge table Regions(\n ResourceLocation: string,\n RegionId: string,\n RegionName: string\n)\n\n// ResourceTypes\n.create-merge table ResourceTypes(\n x_ResourceType: string,\n SingularDisplayName: string,\n PluralDisplayName: string,\n LowerSingularDisplayName: string,\n LowerPluralDisplayName: string,\n IsPreview: bool,\n Description: string,\n IconUri: string\n)\n\n// Services\n.create-merge table Services(\n x_ConsumedService: string,\n x_ResourceType: string,\n ServiceName: string,\n ServiceCategory: string,\n ServiceSubcategory: string,\n PublisherName: string,\n x_PublisherCategory: string,\n x_Environment: string,\n x_ServiceModel: string\n)\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// parse_resourceid\n.create-or-alter function\nwith (docstring = 'Parses an Azure resource ID to extract resource attributes like the name, type, resource group, and subaccount ID.', folder = 'Common')\nparse_resourceid(resourceId: string) {\n let ResourceId = tolower(resourceId);\n // let ResourceId = tolower('/providers/Microsoft.BillingBenefits/savingsPlanOrders/2d2e284b-0638-427e-b8c6-1b874d4f17c8/sp/xxx');\n let SubAccountId = tostring(extract('/subscriptions/[^/]+', 1, ResourceId));\n let x_ResourceGroupName = tostring(extract('/resourcegroups/[^/]+', 1, ResourceId));\n let providerPath = iff(ResourceId !contains '/providers/', '', split(iff(ResourceId startswith '/subscriptions/', strcat('/providers/microsoft.resources/', ResourceId), ResourceId), '/providers/')[-1]);\n let x_ResourceProvider = iff(isempty(providerPath), '', split(providerPath, '/')[0]);\n let tmp_ResourceProviderPath = iff(isempty(providerPath), '', substring(providerPath, strlen(x_ResourceProvider) + 1));\n let segments = split(tmp_ResourceProviderPath, '/');\n let ResourceName = trim(@'/+', replace_string(strcat_array(array_iff(\n dynamic([false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true]),\n segments, dynamic([])), '/'), '//', '/'));\n let x_ResourceTypePath = trim(@'/+', replace_string(strcat_array(array_iff(\n dynamic([true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]),\n segments, dynamic([])), '/'), '//', '/'));\n let xRT = iff(isempty(x_ResourceProvider) or isempty(x_ResourceTypePath), '', strcat(x_ResourceProvider, '/', x_ResourceTypePath));\n // TODO: Remove ResourceType in 0.9\n bag_pack('ResourceId', ResourceId, 'ResourceName', ResourceName, 'ResourceType', xRT, 'SubAccountId', SubAccountId, 'x_ResourceGroupName', x_ResourceGroupName, 'x_ResourceProvider', x_ResourceProvider, 'x_ResourceType', xRT)\n}\n", + "$fxv#8": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| ActualCosts |====================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_raw table -- Create the table if it doesn't exist\n.create-merge table ActualCosts_raw ( ignore: string )\n\n// ActualCosts_raw table -- Remove all columns to allow changing column types\n.alter table ActualCosts_raw ( ignore: string )\n\n// ActualCosts_raw table -- Redefine all columns\n.alter table ActualCosts_raw (\n AccountName: string,\n AccountOwnerId: string,\n AdditionalInfo: string,\n AvailabilityZone: string,\n BillingAccountId: string, \n BillingAccountName: string,\n BillingCurrency: string,\n BillingPeriodEndDate: datetime,\n BillingPeriodStartDate: datetime,\n BillingProfileId: string,\n BillingProfileName: string,\n ChargeType: string,\n ConsumedService: string,\n CostCenter: string,\n Cost: real,\n Date: datetime,\n EffectivePrice: real,\n Frequency: string,\n InvoiceSection: string,\n InvoiceSectionId: string,\n IsAzureCreditEligible: bool,\n MeterCategory: string,\n MeterId: string,\n MeterName: string,\n MeterRegion: string,\n MeterSubCategory: string,\n OfferId: string,\n PartNumber: string,\n PlanName: string,\n Product: string,\n ProductOrderId: string,\n ProductOrderName: string,\n PublisherName: string,\n PublisherType: string,\n Quantity: real,\n ReservationId: string,\n ReservationName: string,\n ResourceGroup: string,\n ResourceId: string,\n ResourceLocation: string,\n ResourceName: string,\n ServiceFamily: string,\n ServiceInfo1: string,\n ServiceInfo2: string,\n SubscriptionId: string,\n SubscriptionName: string,\n Tags: string,\n Term: string,\n UnitOfMeasure: string,\n UnitPrice: real\n)\n\n// ActualCosts_raw ingestion mapping\n.create-or-alter table ActualCosts_raw ingestion parquet mapping \"ActualCosts_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\n]\n```\n\n// ActualCosts_raw retention policy (clear historical data)\n.alter-merge table ActualCosts_raw policy retention softdelete = 0d recoverability = disabled\n\n// ActualCosts_raw retention policy (set the user-defined retention period)\n.alter-merge table ActualCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable ActualCosts_raw streaming ingestion (required for Fabric)\n.alter table ActualCosts_raw policy streamingingestion disable\n\n\n//===| AmortizedCosts |=================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_raw table -- Create the table if it doesn't exist\n.create-merge table AmortizedCosts_raw ( ignore: string )\n\n// AmortizedCosts_raw table -- Remove all columns to allow changing column types\n.alter table AmortizedCosts_raw ( ignore: string )\n\n// AmortizedCosts_raw table -- Redefine all columns\n.alter table AmortizedCosts_raw (\n AccountName: string,\n AccountOwnerId: string,\n AdditionalInfo: string,\n AvailabilityZone: string,\n BillingAccountId: string, \n BillingAccountName: string,\n BillingCurrency: string,\n BillingPeriodEndDate: datetime,\n BillingPeriodStartDate: datetime,\n BillingProfileId: string,\n BillingProfileName: string,\n ChargeType: string,\n ConsumedService: string,\n CostCenter: string,\n Cost: real,\n Date: datetime,\n EffectivePrice: real,\n Frequency: string,\n InvoiceSection: string,\n InvoiceSectionId: string,\n IsAzureCreditEligible: bool,\n MeterCategory: string,\n MeterId: string,\n MeterName: string,\n MeterRegion: string,\n MeterSubCategory: string,\n OfferId: string,\n PartNumber: string,\n PlanName: string,\n Product: string,\n ProductOrderId: string,\n ProductOrderName: string,\n PublisherName: string,\n PublisherType: string,\n Quantity: real,\n ReservationId: string,\n ReservationName: string,\n ResourceGroup: string,\n ResourceId: string,\n ResourceLocation: string,\n ResourceName: string,\n ServiceFamily: string,\n ServiceInfo1: string,\n ServiceInfo2: string,\n SubscriptionId: string,\n SubscriptionName: string,\n Tags: string,\n Term: string,\n UnitOfMeasure: string,\n UnitPrice: real\n)\n\n// AmortizedCosts_raw ingestion mapping\n.create-or-alter table AmortizedCosts_raw ingestion parquet mapping \"AmortizedCosts_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\n]\n```\n\n// AmortizedCosts_raw retention policy (clear historical data)\n.alter-merge table AmortizedCosts_raw policy retention softdelete = 0d recoverability = disabled\n\n// AmortizedCosts_raw retention policy (set the user-defined retention period)\n.alter-merge table AmortizedCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable AmortizedCosts_raw streaming ingestion (required for Fabric)\n.alter table AmortizedCosts_raw policy streamingingestion disable\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_raw table -- Create the table if it doesn't exist\n.create-merge table CommitmentDiscountUsage_raw ( ignore: string )\n\n// CommitmentDiscountUsage_raw table -- Remove all columns to allow changing column types\n.alter table CommitmentDiscountUsage_raw ( ignore: string )\n\n// CommitmentDiscountUsage_raw table -- Redefine all columns\n.alter table CommitmentDiscountUsage_raw (\n InstanceFlexibilityGroup: string,\n InstanceFlexibilityRatio: real,\n InstanceId: string,\n Kind: string,\n ReservationId: string,\n ReservationOrderId: string,\n ReservedHours: real,\n SkuName: string,\n TotalReservedQuantity: real,\n UsageDate: datetime,\n UsedHours: real,\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// CommitmentDiscountUsage_raw ingestion mapping\n.create-or-alter table CommitmentDiscountUsage_raw ingestion parquet mapping \"CommitmentDiscountUsage_raw_mapping\"\n```\n[\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\n { \"Column\": \"InstanceId\", \"Properties\": { \"Field\": \"InstanceId\" } },\n { \"Column\": \"Kind\", \"Properties\": { \"Field\": \"Kind\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\n { \"Column\": \"ReservedHours\", \"Properties\": { \"Field\": \"ReservedHours\" } },\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\n { \"Column\": \"TotalReservedQuantity\", \"Properties\": { \"Field\": \"TotalReservedQuantity\" } },\n { \"Column\": \"UsageDate\", \"Properties\": { \"Field\": \"UsageDate\" } },\n { \"Column\": \"UsedHours\", \"Properties\": { \"Field\": \"UsedHours\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// CommitmentDiscountUsage_raw retention policy (clear historical data)\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = 0d recoverability = disabled\n\n// CommitmentDiscountUsage_raw retention policy (set the user-defined retention period)\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable CommitmentDiscountUsage_raw streaming ingestion (required for Fabric)\n.alter table CommitmentDiscountUsage_raw policy streamingingestion disable\n\n\n//===| Costs |==========================================================================================================\n// Supported versions:\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n// - Tencent: 1.0 -- See https://www.tencentcloud.com/document/product/555/67495 / https://www.tencentcloud.com/document/product/555/67496\n// - Alibaba: 1.0 -- See https://www.alibabacloud.com/help/en/user-center/user-guide/export-alibaba-cloud-standard-billing-focus\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_raw table -- Create the table if it doesn't exist\n.create-merge table Costs_raw ( ignore: string )\n\n// Costs_raw table -- Remove all columns to allow changing column types\n.alter table Costs_raw ( ignore: string )\n\n// Costs_raw table -- Redefine all columns\n.alter table Costs_raw (\n AvailabilityZone: string, // FOCUS 0.5+\n BilledCost: real, // FOCUS 0.5+\n BillingAccountId: string, // FOCUS 0.5+\n BillingAccountName: string, // FOCUS 0.5+\n BillingAccountType: string, // Azure 1.0-preview(v1)+\n BillingCurrency: string, // FOCUS 0.5+\n BillingPeriodEnd: datetime, // FOCUS 0.5+\n BillingPeriodStart: datetime, // FOCUS 0.5+\n CapacityReservationId: string, // FOCUS 1.1+\n CapacityReservationStatus: string, // FOCUS 1.1+\n ChargeCategory: string, // FOCUS 1.0-preview+\n ChargeClass: string, // FOCUS 1.0+\n ChargeDescription: string, // FOCUS 1.0+\n ChargeFrequency: string, // FOCUS 1.0+\n ChargePeriodEnd: datetime, // FOCUS 0.5+\n ChargePeriodStart: datetime, // FOCUS 0.5+\n ChargeSubcategory: string, // FOCUS 1.0-preview only\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview+\n CommitmentDiscountId: string, // FOCUS 1.0-preview+\n CommitmentDiscountName: string, // FOCUS 1.0-preview+\n CommitmentDiscountQuantity: real, // FOCUS 1.1+\n CommitmentDiscountStatus: string, // FOCUS 1.0+\n CommitmentDiscountType: string, // FOCUS 1.0-preview+\n CommitmentDiscountUnit: string, // FOCUS 1.1+\n ConsumedQuantity: real, // FOCUS 1.0+\n ConsumedUnit: string, // FOCUS 1.0+\n ContractedCost: real, // FOCUS 1.0+\n ContractedUnitPrice: real, // FOCUS 1.0+\n EffectiveCost: real, // FOCUS 1.0-preview+\n InvoiceId: string, // FOCUS 1.2+\n InvoiceIssuerName: string, // FOCUS 0.5+\n ListCost: real, // FOCUS 1.0-preview+\n ListUnitPrice: real, // FOCUS 1.0-preview+\n PricingCategory: string, // FOCUS 1.0-preview+\n PricingCurrency: string, // FOCUS 1.2+\n PricingQuantity: real, // FOCUS 1.0-preview+\n PricingUnit: string, // FOCUS 1.0-preview+\n ProviderName: string, // FOCUS 0.5+\n PublisherName: string, // FOCUS 0.5+\n Region: string, // FOCUS 0.5-1.0-preview (deprecated)\n RegionId: string, // FOCUS 1.0+\n RegionName: string, // FOCUS 1.0+\n ResourceId: string, // FOCUS 0.5+\n ResourceName: string, // FOCUS 0.5+\n ResourceType: string, // FOCUS 1.0-preview+\n ServiceCategory: string, // FOCUS 0.5+\n ServiceName: string, // FOCUS 0.5+\n ServiceSubcategory: string, // FOCUS 1.1+\n SkuId: string, // FOCUS 1.0-preview+\n SkuMeter: string, // FOCUS 1.1+\n SkuPriceDetails: string, // FOCUS 1.1+\n SkuPriceId: string, // FOCUS 1.0-preview+\n SubAccountId: string, // FOCUS 0.5+\n SubAccountName: string, // FOCUS 0.5+\n SubAccountType: string, // Azure 1.0-preview(v1)+\n Tags: string, // FOCUS 1.0-preview+\n UsageAmount: real, // GCP Jan 2024 -- Removed Mar 2024 (UsageQuantity)\n UsageQuantity: real, // FOCUS 1.0-preview only\n UsageUnit: string, // FOCUS 1.0-preview only\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_AmortizationClass: string, // Azure 1.2-preview+\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingItemCode: string, // Alibaba 1.0+\n x_BillingItemName: string, // Alibaba 1.0+\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_CommodityCode: string, // Alibaba 1.0+\n x_CommodityName: string, // Alibaba 1.0+\n x_ComponentName: string, // Tencent 1.0+\n x_ComponentType: string, // Tencent 1.0+\n x_ContractedCostInUsd: real, // Azure 1.0+\n x_Cost: real, // GCP Jan 2024 -- Removed Jun 2024 (ContractedCost)\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: string, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_CostType: string, // GCP Jan 2024\n x_Credits: string, // GCP Jan 2024\n x_CurrencyConversionRate: real, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: string, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0+\n x_InstanceID: string, // Alibaba 1.0+\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_OnDemandCost: real, // Azure 1.0-preview(v1) only\n x_OnDemandCostInUsd: real, // Azure 1.0-preview(v1) only\n x_OnDemandUnitPrice: real, // Azure 1.0-preview(v1) only\n x_Operation: string, // AWS 1.0\n x_OwnerAccountID: string, // Tencent 1.0+\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\n x_PricingCurrency: string, // Azure 1.0-preview(v1)-1.0r2\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServiceModel: string, // Azure 1.2-preview+\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: string, // Azure 1.0-preview(v1)-1.2-preview\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterName: string, // Azure 1.0-preview(v1)-1.0r2\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuPlanName: string, // Azure 1.2-preview+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string, // Hubs v1_0+\n x_SubproductName: string, // Tencent 1.0+ // cSpell:ignore Subproduct\n x_UsageType: string // AWS 1.0\n)\n\n// Costs_raw ingestion mapping\n.create-or-alter table Costs_raw ingestion parquet mapping \"Costs_raw_mapping\"\n```\n[\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BilledCost\", \"Properties\": { \"Field\": \"BilledCost\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingAccountType\", \"Properties\": { \"Field\": \"BillingAccountType\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEnd\", \"Properties\": { \"Field\": \"BillingPeriodEnd\" } },\n { \"Column\": \"BillingPeriodStart\", \"Properties\": { \"Field\": \"BillingPeriodStart\" } },\n { \"Column\": \"CapacityReservationId\", \"Properties\": { \"Field\": \"CapacityReservationId\" } },\n { \"Column\": \"CapacityReservationStatus\", \"Properties\": { \"Field\": \"CapacityReservationStatus\" } },\n { \"Column\": \"ChargeCategory\", \"Properties\": { \"Field\": \"ChargeCategory\" } },\n { \"Column\": \"ChargeClass\", \"Properties\": { \"Field\": \"ChargeClass\" } },\n { \"Column\": \"ChargeDescription\", \"Properties\": { \"Field\": \"ChargeDescription\" } },\n { \"Column\": \"ChargeFrequency\", \"Properties\": { \"Field\": \"ChargeFrequency\" } },\n { \"Column\": \"ChargePeriodEnd\", \"Properties\": { \"Field\": \"ChargePeriodEnd\" } },\n { \"Column\": \"ChargePeriodStart\", \"Properties\": { \"Field\": \"ChargePeriodStart\" } },\n { \"Column\": \"ChargeSubcategory\", \"Properties\": { \"Field\": \"ChargeSubcategory\" } },\n { \"Column\": \"CommitmentDiscountCategory\", \"Properties\": { \"Field\": \"CommitmentDiscountCategory\" } },\n { \"Column\": \"CommitmentDiscountId\", \"Properties\": { \"Field\": \"CommitmentDiscountId\" } },\n { \"Column\": \"CommitmentDiscountName\", \"Properties\": { \"Field\": \"CommitmentDiscountName\" } },\n { \"Column\": \"CommitmentDiscountQuantity\", \"Properties\": { \"Field\": \"CommitmentDiscountQuantity\" } },\n { \"Column\": \"CommitmentDiscountStatus\", \"Properties\": { \"Field\": \"CommitmentDiscountStatus\" } },\n { \"Column\": \"CommitmentDiscountType\", \"Properties\": { \"Field\": \"CommitmentDiscountType\" } },\n { \"Column\": \"CommitmentDiscountUnit\", \"Properties\": { \"Field\": \"CommitmentDiscountUnit\" } },\n { \"Column\": \"ConsumedQuantity\", \"Properties\": { \"Field\": \"ConsumedQuantity\" } },\n { \"Column\": \"ConsumedUnit\", \"Properties\": { \"Field\": \"ConsumedUnit\" } },\n { \"Column\": \"ContractedCost\", \"Properties\": { \"Field\": \"ContractedCost\" } },\n { \"Column\": \"ContractedUnitPrice\", \"Properties\": { \"Field\": \"ContractedUnitPrice\" } },\n { \"Column\": \"EffectiveCost\", \"Properties\": { \"Field\": \"EffectiveCost\" } },\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\n { \"Column\": \"InvoiceIssuerName\", \"Properties\": { \"Field\": \"InvoiceIssuerName\" } },\n { \"Column\": \"ListCost\", \"Properties\": { \"Field\": \"ListCost\" } },\n { \"Column\": \"ListUnitPrice\", \"Properties\": { \"Field\": \"ListUnitPrice\" } },\n { \"Column\": \"PricingCategory\", \"Properties\": { \"Field\": \"PricingCategory\" } },\n { \"Column\": \"PricingCurrency\", \"Properties\": { \"Field\": \"PricingCurrency\" } },\n { \"Column\": \"PricingQuantity\", \"Properties\": { \"Field\": \"PricingQuantity\" } },\n { \"Column\": \"PricingUnit\", \"Properties\": { \"Field\": \"PricingUnit\" } },\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\n { \"Column\": \"RegionId\", \"Properties\": { \"Field\": \"RegionId\" } },\n { \"Column\": \"RegionName\", \"Properties\": { \"Field\": \"RegionName\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\n { \"Column\": \"ServiceCategory\", \"Properties\": { \"Field\": \"ServiceCategory\" } },\n { \"Column\": \"ServiceName\", \"Properties\": { \"Field\": \"ServiceName\" } },\n { \"Column\": \"ServiceSubcategory\", \"Properties\": { \"Field\": \"ServiceSubcategory\" } },\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\n { \"Column\": \"SkuMeter\", \"Properties\": { \"Field\": \"SkuMeter\" } },\n { \"Column\": \"SkuPriceDetails\", \"Properties\": { \"Field\": \"SkuPriceDetails\" } },\n { \"Column\": \"SkuPriceId\", \"Properties\": { \"Field\": \"SkuPriceId\" } },\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\n { \"Column\": \"SubAccountType\", \"Properties\": { \"Field\": \"SubAccountType\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"UsageAmount\", \"Properties\": { \"Field\": \"UsageAmount\" } },\n { \"Column\": \"UsageQuantity\", \"Properties\": { \"Field\": \"UsageQuantity\" } },\n { \"Column\": \"UsageUnit\", \"Properties\": { \"Field\": \"UsageUnit\" } },\n { \"Column\": \"x_AccountId\", \"Properties\": { \"Field\": \"x_AccountId\" } },\n { \"Column\": \"x_AccountName\", \"Properties\": { \"Field\": \"x_AccountName\" } },\n { \"Column\": \"x_AccountOwnerId\", \"Properties\": { \"Field\": \"x_AccountOwnerId\" } },\n { \"Column\": \"x_AmortizationClass\", \"Properties\": { \"Field\": \"x_AmortizationClass\" } },\n { \"Column\": \"x_BilledCostInUsd\", \"Properties\": { \"Field\": \"x_BilledCostInUsd\" } },\n { \"Column\": \"x_BilledUnitPrice\", \"Properties\": { \"Field\": \"x_BilledUnitPrice\" } },\n { \"Column\": \"x_BillingAccountId\", \"Properties\": { \"Field\": \"x_BillingAccountId\" } },\n { \"Column\": \"x_BillingAccountName\", \"Properties\": { \"Field\": \"x_BillingAccountName\" } },\n { \"Column\": \"x_BillingExchangeRate\", \"Properties\": { \"Field\": \"x_BillingExchangeRate\" } },\n { \"Column\": \"x_BillingExchangeRateDate\", \"Properties\": { \"Field\": \"x_BillingExchangeRateDate\" } },\n { \"Column\": \"x_BillingItemCode\", \"Properties\": { \"Field\": \"x_BillingItemCode\" } },\n { \"Column\": \"x_BillingItemName\", \"Properties\": { \"Field\": \"x_BillingItemName\" } },\n { \"Column\": \"x_BillingProfileId\", \"Properties\": { \"Field\": \"x_BillingProfileId\" } },\n { \"Column\": \"x_BillingProfileName\", \"Properties\": { \"Field\": \"x_BillingProfileName\" } },\n { \"Column\": \"x_ChargeId\", \"Properties\": { \"Field\": \"x_ChargeId\" } },\n { \"Column\": \"x_ContractedCostInUsd\", \"Properties\": { \"Field\": \"x_ContractedCostInUsd\" } },\n { \"Column\": \"x_CommodityCode\", \"Properties\": { \"Field\": \"x_CommodityCode\" } },\n { \"Column\": \"x_CommodityName\", \"Properties\": { \"Field\": \"x_CommodityName\" } },\n { \"Column\": \"x_ComponentName\", \"Properties\": { \"Field\": \"x_ComponentName\" } },\n { \"Column\": \"x_ComponentType\", \"Properties\": { \"Field\": \"x_ComponentType\" } },\n { \"Column\": \"x_Cost\", \"Properties\": { \"Field\": \"x_Cost\" } },\n { \"Column\": \"x_CostAllocationRuleName\", \"Properties\": { \"Field\": \"x_CostAllocationRuleName\" } },\n { \"Column\": \"x_CostCategories\", \"Properties\": { \"Field\": \"x_CostCategories\" } },\n { \"Column\": \"x_CostCenter\", \"Properties\": { \"Field\": \"x_CostCenter\" } },\n { \"Column\": \"x_Credits\", \"Properties\": { \"Field\": \"x_Credits\" } },\n { \"Column\": \"x_CostType\", \"Properties\": { \"Field\": \"x_CostType\" } },\n { \"Column\": \"x_CurrencyConversionRate\", \"Properties\": { \"Field\": \"x_CurrencyConversionRate\" } },\n { \"Column\": \"x_CustomerId\", \"Properties\": { \"Field\": \"x_CustomerId\" } },\n { \"Column\": \"x_CustomerName\", \"Properties\": { \"Field\": \"x_CustomerName\" } },\n { \"Column\": \"x_Discount\", \"Properties\": { \"Field\": \"x_Discount\" } },\n { \"Column\": \"x_EffectiveCostInUsd\", \"Properties\": { \"Field\": \"x_EffectiveCostInUsd\" } },\n { \"Column\": \"x_EffectiveUnitPrice\", \"Properties\": { \"Field\": \"x_EffectiveUnitPrice\" } },\n { \"Column\": \"x_ExportTime\", \"Properties\": { \"Field\": \"x_ExportTime\" } },\n { \"Column\": \"x_InstanceID\", \"Properties\": { \"Field\": \"x_InstanceID\" } },\n { \"Column\": \"x_InvoiceId\", \"Properties\": { \"Field\": \"x_InvoiceId\" } },\n { \"Column\": \"x_InvoiceIssuerId\", \"Properties\": { \"Field\": \"x_InvoiceIssuerId\" } },\n { \"Column\": \"x_InvoiceSectionId\", \"Properties\": { \"Field\": \"x_InvoiceSectionId\" } },\n { \"Column\": \"x_InvoiceSectionName\", \"Properties\": { \"Field\": \"x_InvoiceSectionName\" } },\n { \"Column\": \"x_ListCostInUsd\", \"Properties\": { \"Field\": \"x_ListCostInUsd\" } },\n { \"Column\": \"x_Location\", \"Properties\": { \"Field\": \"x_Location\" } },\n { \"Column\": \"x_OnDemandCost\", \"Properties\": { \"Field\": \"x_OnDemandCost\" } },\n { \"Column\": \"x_OnDemandCostInUsd\", \"Properties\": { \"Field\": \"x_OnDemandCostInUsd\" } },\n { \"Column\": \"x_OnDemandUnitPrice\", \"Properties\": { \"Field\": \"x_OnDemandUnitPrice\" } },\n { \"Column\": \"x_Operation\", \"Properties\": { \"Field\": \"x_Operation\" } },\n { \"Column\": \"x_OwnerAccountID\", \"Properties\": { \"Field\": \"x_OwnerAccountID\" } },\n { \"Column\": \"x_PartnerCreditApplied\", \"Properties\": { \"Field\": \"x_PartnerCreditApplied\" } },\n { \"Column\": \"x_PartnerCreditRate\", \"Properties\": { \"Field\": \"x_PartnerCreditRate\" } },\n { \"Column\": \"x_PricingBlockSize\", \"Properties\": { \"Field\": \"x_PricingBlockSize\" } },\n { \"Column\": \"x_PricingCurrency\", \"Properties\": { \"Field\": \"x_PricingCurrency\" } },\n { \"Column\": \"x_PricingSubcategory\", \"Properties\": { \"Field\": \"x_PricingSubcategory\" } },\n { \"Column\": \"x_PricingUnitDescription\", \"Properties\": { \"Field\": \"x_PricingUnitDescription\" } },\n { \"Column\": \"x_Project\", \"Properties\": { \"Field\": \"x_Project\" } },\n { \"Column\": \"x_PublisherCategory\", \"Properties\": { \"Field\": \"x_PublisherCategory\" } },\n { \"Column\": \"x_PublisherId\", \"Properties\": { \"Field\": \"x_PublisherId\" } },\n { \"Column\": \"x_ResellerId\", \"Properties\": { \"Field\": \"x_ResellerId\" } },\n { \"Column\": \"x_ResellerName\", \"Properties\": { \"Field\": \"x_ResellerName\" } },\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\n { \"Column\": \"x_ResourceType\", \"Properties\": { \"Field\": \"x_ResourceType\" } },\n { \"Column\": \"x_ServiceCode\", \"Properties\": { \"Field\": \"x_ServiceCode\" } },\n { \"Column\": \"x_ServiceId\", \"Properties\": { \"Field\": \"x_ServiceId\" } },\n { \"Column\": \"x_ServiceModel\", \"Properties\": { \"Field\": \"x_ServiceModel\" } },\n { \"Column\": \"x_ServicePeriodEnd\", \"Properties\": { \"Field\": \"x_ServicePeriodEnd\" } },\n { \"Column\": \"x_ServicePeriodStart\", \"Properties\": { \"Field\": \"x_ServicePeriodStart\" } },\n { \"Column\": \"x_SkuDescription\", \"Properties\": { \"Field\": \"x_SkuDescription\" } },\n { \"Column\": \"x_SkuDetails\", \"Properties\": { \"Field\": \"x_SkuDetails\" } },\n { \"Column\": \"x_SkuIsCreditEligible\", \"Properties\": { \"Field\": \"x_SkuIsCreditEligible\" } },\n { \"Column\": \"x_SkuMeterCategory\", \"Properties\": { \"Field\": \"x_SkuMeterCategory\" } },\n { \"Column\": \"x_SkuMeterId\", \"Properties\": { \"Field\": \"x_SkuMeterId\" } },\n { \"Column\": \"x_SkuMeterName\", \"Properties\": { \"Field\": \"x_SkuMeterName\" } },\n { \"Column\": \"x_SkuMeterSubcategory\", \"Properties\": { \"Field\": \"x_SkuMeterSubcategory\" } },\n { \"Column\": \"x_SkuOfferId\", \"Properties\": { \"Field\": \"x_SkuOfferId\" } },\n { \"Column\": \"x_SkuOrderId\", \"Properties\": { \"Field\": \"x_SkuOrderId\" } },\n { \"Column\": \"x_SkuOrderName\", \"Properties\": { \"Field\": \"x_SkuOrderName\" } },\n { \"Column\": \"x_SkuPartNumber\", \"Properties\": { \"Field\": \"x_SkuPartNumber\" } },\n { \"Column\": \"x_SkuPlanName\", \"Properties\": { \"Field\": \"x_SkuPlanName\" } },\n { \"Column\": \"x_SkuRegion\", \"Properties\": { \"Field\": \"x_SkuRegion\" } },\n { \"Column\": \"x_SkuServiceFamily\", \"Properties\": { \"Field\": \"x_SkuServiceFamily\" } },\n { \"Column\": \"x_SkuTerm\", \"Properties\": { \"Field\": \"x_SkuTerm\" } },\n { \"Column\": \"x_SkuTier\", \"Properties\": { \"Field\": \"x_SkuTier\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } },\n { \"Column\": \"x_SubproductName\", \"Properties\": { \"Field\": \"x_SubproductName\" } },\n { \"Column\": \"x_UsageType\", \"Properties\": { \"Field\": \"x_UsageType\" } }\n]\n```\n\n// Costs_raw retention policy (clear historical data)\n.alter-merge table Costs_raw policy retention softdelete = 0d recoverability = disabled\n\n// Costs_raw retention policy (set the user-defined retention period)\n.alter-merge table Costs_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Costs_raw streaming ingestion (required for Fabric)\n.alter table Costs_raw policy streamingingestion disable\n\n\n//===| Prices |=========================================================================================================\n// NOTE: Must be before cost details.\n//\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_raw table -- Create the table if it doesn't exist\n.create-merge table Prices_raw ( ignore: string )\n\n// Prices_raw table -- Remove all columns to allow changing column types\n.alter table Prices_raw ( ignore: string )\n\n// Prices_raw table -- Redefine all columns\n.alter table Prices_raw (\n BasePrice: real, // Azure EA + MCA\n BillingAccountId: string, // Azure MCA\n BillingAccountName: string, // Azure MCA\n BillingCurrency: string, // Azure MCA\n BillingProfileId: string, // Azure MCA\n BillingProfileName: string, // Azure MCA\n Currency: string, // Azure MCA\n CurrencyCode: string, // Azure EA\n EffectiveEndDate: datetime, // Azure MCA\n EffectiveStartDate: datetime, // Azure EA + MCA\n EnrollmentNumber: string, // Azure EA\n IncludedQuantity: real, // Azure EA\n MarketPrice: real, // Azure EA + MCA\n MeterCategory: string, // Azure EA + MCA\n MeterId: string, // Azure MCA\n MeterID: string, // Azure EA\n MeterName: string, // Azure EA + MCA\n MeterRegion: string, // Azure EA + MCA\n MeterSubCategory: string, // Azure EA + MCA\n MeterType: string, // Azure EA + MCA\n OfferID: string, // Azure EA\n PartNumber: string, // Azure EA\n PriceType: string, // Azure EA + MCA\n Product: string, // Azure EA + MCA\n ProductId: string, // Azure MCA\n ProductID: string, // Azure EA\n ServiceFamily: string, // Azure EA + MCA\n SkuId: string, // Azure MCA\n SkuID: string, // Azure EA\n Term: string, // Azure EA + MCA\n TierMinimumUnits: real, // Azure MCA\n UnitOfMeasure: string, // Azure EA + MCA\n UnitPrice: real, // Azure EA + MCA\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Prices_raw ingestion mapping\n.create-or-alter table Prices_raw ingestion parquet mapping \"Prices_raw_mapping\"\n```\n[\n { \"Column\": \"BasePrice\", \"Properties\": { \"Field\": \"BasePrice\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\n { \"Column\": \"CurrencyCode\", \"Properties\": { \"Field\": \"CurrencyCode\" } },\n { \"Column\": \"EffectiveEndDate\", \"Properties\": { \"Field\": \"EffectiveEndDate\" } },\n { \"Column\": \"EffectiveStartDate\", \"Properties\": { \"Field\": \"EffectiveStartDate\" } },\n { \"Column\": \"EnrollmentNumber\", \"Properties\": { \"Field\": \"EnrollmentNumber\" } },\n { \"Column\": \"IncludedQuantity\", \"Properties\": { \"Field\": \"IncludedQuantity\" } },\n { \"Column\": \"MarketPrice\", \"Properties\": { \"Field\": \"MarketPrice\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterID\", \"Properties\": { \"Field\": \"MeterID\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"MeterType\", \"Properties\": { \"Field\": \"MeterType\" } },\n { \"Column\": \"OfferID\", \"Properties\": { \"Field\": \"OfferID\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PriceType\", \"Properties\": { \"Field\": \"PriceType\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductId\", \"Properties\": { \"Field\": \"ProductId\" } },\n { \"Column\": \"ProductID\", \"Properties\": { \"Field\": \"ProductID\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\n { \"Column\": \"SkuID\", \"Properties\": { \"Field\": \"SkuID\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"TierMinimumUnits\", \"Properties\": { \"Field\": \"TierMinimumUnits\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Prices_raw retention policy (clear historical data)\n.alter-merge table Prices_raw policy retention softdelete = 0d recoverability = disabled\n\n// Prices_raw retention policy (set the user-defined retention period)\n.alter-merge table Prices_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Prices_raw streaming ingestion (required for Fabric)\n.alter table Prices_raw policy streamingingestion disable\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_raw table -- Create the table if it doesn't exist\n.create-merge table Recommendations_raw ( ignore: string )\n\n// Recommendations_raw table -- Remove all columns to allow changing column types\n.alter table Recommendations_raw ( ignore: string )\n\n// Recommendations_raw table -- Redefine all columns\n.alter table Recommendations_raw (\n CostWithNoReservedInstances: real, // MS CM EA resv reco 2024-05-01\n CostWithNoReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n FirstUsageDate: datetime, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n InstanceFlexibilityGroup: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n InstanceFlexibilityRatio: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n Location: string, // MS CM EA+MCA resv reco 2024-05-01\n LookBackPeriod: string, // MS CM EA+MCA resv reco 2024-05-01\n MeterId: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n NetSavings: real, // MS CM EA resv reco 2024-05-01\n NetSavingsJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n NormalizedSize: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n ProviderName: string, // Hubs v1_2\n RecommendedQuantity: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n RecommendedQuantityNormalized: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n ResourceId: string, // Hubs v1_2\n ResourceName: string, // Hubs v1_2\n ResourceType: string, // Hubs v1_2, MS CM EA+MCA resv reco 2024-05-01\n Scope: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n SKU: string, // MS CM EA resv reco 2024-05-01\n SkuName: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces\n SkuProperties: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n SubAccountId: string, // Hubs v1_2\n SubAccountName: string, // Hubs v1_2\n SubscriptionId: string, // MS CM EA+MCA resv reco 2024-05-01\n Term: string, // MS CM EA+MCA resv reco 2024-05-01\n TotalCostWithReservedInstances: real, // MS CM EA resv reco 2024-05-01\n TotalCostWithReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n x_EffectiveCostAfter: real, // Hubs v1_2\n x_EffectiveCostBefore: real, // Hubs v1_2\n x_EffectiveCostSavings: real, // Hubs v1_2\n x_RecommendationCategory: string, // Hubs v1_2\n x_RecommendationDate: datetime, // Hubs v1_2\n x_RecommendationDescription: string, // Hubs v1_2\n x_RecommendationDetails: dynamic, // Hubs v1_2\n x_RecommendationId: string, // Hubs v1_2\n x_ResourceGroupName: string, // Hubs v1_2\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Recommendations_raw ingestion mapping\n.create-or-alter table Recommendations_raw ingestion parquet mapping \"Recommendations_raw_mapping\"\n```\n[\n { \"Column\": \"CostWithNoReservedInstances\", \"Properties\": { \"Field\": \"CostWithNoReservedInstances\" } },\n { \"Column\": \"CostWithNoReservedInstancesJson\", \"Properties\": { \"Field\": \"CostWithNoReservedInstancesJson\" } },\n { \"Column\": \"FirstUsageDate\", \"Properties\": { \"Field\": \"FirstUsageDate\" } },\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\n { \"Column\": \"Location\", \"Properties\": { \"Field\": \"Location\" } },\n { \"Column\": \"LookBackPeriod\", \"Properties\": { \"Field\": \"LookBackPeriod\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"NetSavings\", \"Properties\": { \"Field\": \"NetSavings\" } },\n { \"Column\": \"NetSavingsJson\", \"Properties\": { \"Field\": \"NetSavingsJson\" } },\n { \"Column\": \"NormalizedSize\", \"Properties\": { \"Field\": \"NormalizedSize\" } },\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\n { \"Column\": \"RecommendedQuantity\", \"Properties\": { \"Field\": \"RecommendedQuantity\" } },\n { \"Column\": \"RecommendedQuantityNormalized\", \"Properties\": { \"Field\": \"RecommendedQuantityNormalized\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\n { \"Column\": \"Scope\", \"Properties\": { \"Field\": \"Scope\" } },\n { \"Column\": \"SKU\", \"Properties\": { \"Field\": \"SKU\" } },\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\n { \"Column\": \"SkuProperties\", \"Properties\": { \"Field\": \"SkuProperties\" } },\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"TotalCostWithReservedInstances\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstances\" } },\n { \"Column\": \"TotalCostWithReservedInstancesJson\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstancesJson\" } },\n { \"Column\": \"x_EffectiveCostAfter\", \"Properties\": { \"Field\": \"x_EffectiveCostAfter\" } },\n { \"Column\": \"x_EffectiveCostBefore\", \"Properties\": { \"Field\": \"x_EffectiveCostBefore\" } },\n { \"Column\": \"x_EffectiveCostSavings\", \"Properties\": { \"Field\": \"x_EffectiveCostSavings\" } },\n { \"Column\": \"x_RecommendationCategory\", \"Properties\": { \"Field\": \"x_RecommendationCategory\" } },\n { \"Column\": \"x_RecommendationDate\", \"Properties\": { \"Field\": \"x_RecommendationDate\" } },\n { \"Column\": \"x_RecommendationDescription\", \"Properties\": { \"Field\": \"x_RecommendationDescription\" } },\n { \"Column\": \"x_RecommendationDetails\", \"Properties\": { \"Field\": \"x_RecommendationDetails\" } },\n { \"Column\": \"x_RecommendationId\", \"Properties\": { \"Field\": \"x_RecommendationId\" } },\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Recommendations_raw retention policy (clear historical data)\n.alter-merge table Recommendations_raw policy retention softdelete = 0d recoverability = disabled\n\n// Recommendations_raw retention policy (set the user-defined retention period)\n.alter-merge table Recommendations_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Recommendations_raw streaming ingestion (required for Fabric)\n.alter table Recommendations_raw policy streamingingestion disable\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_raw table -- Create the table if it doesn't exist\n.create-merge table Transactions_raw ( ignore: string )\n\n// Transactions_raw table -- Remove all columns to allow changing column types\n.alter table Transactions_raw ( ignore: string )\n\n// Transactions_raw table -- Redefine all columns\n.alter table Transactions_raw (\n AccountName: string, // MS CM EA resv trans 2023-05-01\n AccountOwnerEmail: string, // MS CM EA resv trans 2023-05-01\n Amount: real, // MS CM EA+MCA resv trans 2023-05-01\n ArmSkuName: string, // MS CM EA+MCA resv trans 2023-05-01\n BillingFrequency: string, // MS CM EA+MCA resv trans 2023-05-01\n BillingMonth: string, // MS CM EA resv trans 2023-05-01\n BillingProfileId: string, // MS CM MCA resv trans 2023-05-01\n BillingProfileName: string, // MS CM MCA resv trans 2023-05-01\n CostCenter: string, // MS CM EA resv trans 2023-05-01\n Currency: string, // MS CM EA+MCA resv trans 2023-05-01\n CurrentEnrollmentId: string, // MS CM EA resv trans 2023-05-01\n DepartmentName: string, // MS CM EA resv trans 2023-05-01\n Description: string, // MS CM EA+MCA resv trans 2023-05-01\n EventDate: datetime, // MS CM EA+MCA resv trans 2023-05-01\n EventType: string, // MS CM EA+MCA resv trans 2023-05-01\n Invoice: string, // MS CM EA+MCA resv trans 2023-05-01\n InvoiceId: string, // MS CM EA+MCA resv trans 2023-05-01\n InvoiceSectionId: string, // MS CM MCA resv trans 2023-05-01\n InvoiceSectionName: string, // MS CM MCA resv trans 2023-05-01\n MonetaryCommitment: real, // MS CM EA resv trans 2023-05-01\n Overage: real, // MS CM EA resv trans 2023-05-01\n PurchasingEnrollment: string, // MS CM EA resv trans 2023-05-01\n PurchasingSubscriptionGuid: string, // MS CM EA+MCA resv trans 2023-05-01\n PurchasingSubscriptionName: string, // MS CM EA+MCA resv trans 2023-05-01\n Quantity: real, // MS CM EA+MCA resv trans 2023-05-01\n Region: string, // MS CM EA+MCA resv trans 2023-05-01\n ReservationOrderId: string, // MS CM EA+MCA resv trans 2023-05-01\n ReservationOrderName: string, // MS CM EA+MCA resv trans 2023-05-01\n Term: string, // MS CM EA+MCA resv trans 2023-05-01\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Transactions_raw ingestion mapping\n.create-or-alter table Transactions_raw ingestion parquet mapping \"Transactions_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerEmail\", \"Properties\": { \"Field\": \"AccountOwnerEmail\" } },\n { \"Column\": \"Amount\", \"Properties\": { \"Field\": \"Amount\" } },\n { \"Column\": \"ArmSkuName\", \"Properties\": { \"Field\": \"ArmSkuName\" } },\n { \"Column\": \"BillingFrequency\", \"Properties\": { \"Field\": \"BillingFrequency\" } },\n { \"Column\": \"BillingMonth\", \"Properties\": { \"Field\": \"BillingMonth\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\n { \"Column\": \"CurrentEnrollmentId\", \"Properties\": { \"Field\": \"CurrentEnrollmentId\" } },\n { \"Column\": \"DepartmentName\", \"Properties\": { \"Field\": \"DepartmentName\" } },\n { \"Column\": \"Description\", \"Properties\": { \"Field\": \"Description\" } },\n { \"Column\": \"EventDate\", \"Properties\": { \"Field\": \"EventDate\" } },\n { \"Column\": \"EventType\", \"Properties\": { \"Field\": \"EventType\" } },\n { \"Column\": \"Invoice\", \"Properties\": { \"Field\": \"Invoice\" } },\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"InvoiceSectionName\", \"Properties\": { \"Field\": \"InvoiceSectionName\" } },\n { \"Column\": \"MonetaryCommitment\", \"Properties\": { \"Field\": \"MonetaryCommitment\" } },\n { \"Column\": \"Overage\", \"Properties\": { \"Field\": \"Overage\" } },\n { \"Column\": \"PurchasingEnrollment\", \"Properties\": { \"Field\": \"PurchasingEnrollment\" } },\n { \"Column\": \"PurchasingSubscriptionGuid\", \"Properties\": { \"Field\": \"PurchasingSubscriptionGuid\" } },\n { \"Column\": \"PurchasingSubscriptionName\", \"Properties\": { \"Field\": \"PurchasingSubscriptionName\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\n { \"Column\": \"ReservationOrderName\", \"Properties\": { \"Field\": \"ReservationOrderName\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Transactions_raw retention policy (clear historical data)\n.alter-merge table Transactions_raw policy retention softdelete = 0d recoverability = disabled\n\n// Transactions_raw retention policy (set the user-defined retention period)\n.alter-merge table Transactions_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Transactions_raw streaming ingestion (required for Fabric)\n.alter table Transactions_raw policy streamingingestion disable\n\n", + "$fxv#9": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Prices |=========================================================================================================\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All prices transformed to FOCUS 1.0. Use Prices_transform_v1_2() instead.', folder='Prices')\nPrices_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n let prices = materialize(\n Prices_raw\n //\n // Change real to decimal\n | extend\n BasePrice = todecimal(BasePrice),\n IncludedQuantity = todecimal(IncludedQuantity),\n MarketPrice = todecimal(MarketPrice),\n TierMinimumUnits = todecimal(TierMinimumUnits),\n UnitPrice = todecimal(UnitPrice)\n //\n | extend x_SkuId = coalesce(SkuId, SkuID)\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\n | extend x_SkuTerm = isoMonths(Term)\n | project-rename\n x_BaseUnitPrice = BasePrice,\n x_EffectivePeriodEnd = EffectiveEndDate,\n x_EffectivePeriodStart = EffectiveStartDate,\n x_PricingUnitDescription = UnitOfMeasure,\n x_SkuIncludedQuantity = IncludedQuantity,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuMeterType = MeterType,\n x_SkuOfferId = OfferID,\n x_SkuPartNumber = PartNumber,\n x_SkuPriceType = PriceType,\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTier = TierMinimumUnits\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, todecimal('')) // UnitPrice for savings plan is not the on-demand unit price\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, todecimal('')) // MarketPrice for savings plan is not the list price\n | extend ChargeCategory = case(\n x_SkuPriceType == 'Consumption', 'Usage',\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\n ''\n )\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\n //\n // Get latest ingested row based on the unique ID\n | extend x_IngestionTime = ingestion_time()\n );\n //\n // Meters for reservations and savings plans to identify commitment eligibility\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\n //\n // Copy list/base/contracted prices from on-demand SKUs\n prices\n | where x_SkuPriceType == 'SavingsPlan'\n // If we use join, specify the shuffle key\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\n //\n // Calculate commitment discount elgibility\n // TODO: Would a join be faster?\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\n //\n // Add PricingUnit and x_PricingBlockSize\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\n | lookup kind=leftouter (PricingUnits | extend x_PricingBlockSize = todecimal(x_PricingBlockSize)) on x_PricingUnitDescription\n //\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, todecimal('')) // Savings plan prices are for the effective price, not the contracted price\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\n | project\n BillingAccountId = tolower(case(\n BillingProfileId startswith '/', BillingProfileId,\n BillingAccountId startswith '/', BillingAccountId,\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\n )),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\n ChargeCategory,\n CommitmentDiscountCategory = case(\n x_SkuPriceType == 'ReservedInstance', 'Usage',\n x_SkuPriceType == 'SavingsPlan', 'Spend',\n ''\n ),\n CommitmentDiscountType = case(\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\n ''\n ),\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory = case(\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed',\n ''\n ),\n PricingUnit,\n SkuId = coalesce(ProductId, ProductID),\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement = case(\n strlen(x_BillingAccountId) > 32, 'MCA',\n strlen(x_BillingAccountId) < 32, 'EA',\n 'Unknown'\n ),\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingCurrency = coalesce(Currency, CurrencyCode), // CurrencyCode last as a fallback only\n x_PricingSubcategory = case(\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\n ''\n ),\n x_PricingUnitDescription,\n x_SkuDescription = Product,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\n}\n\n// Prices_final_v1_0 table\n.create-merge table Prices_final_v1_0 (\n BillingAccountId: string,\n BillingAccountName: string,\n BillingCurrency: string,\n ChargeCategory: string,\n CommitmentDiscountCategory: string,\n CommitmentDiscountType: string,\n ContractedUnitPrice: decimal,\n ListUnitPrice: decimal,\n PricingCategory: string,\n PricingUnit: string,\n SkuId: string,\n SkuPriceId: string,\n SkuPriceIdv2: string, // Hubs add-on\n x_BaseUnitPrice: decimal, // Azure\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure MCA\n x_BillingProfileId: string, // Azure MCA\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_ContractedUnitPriceDiscount: decimal, // Hubs add-on\n x_ContractedUnitPriceDiscountPercent: decimal, // Hubs add-on\n x_EffectivePeriodEnd: datetime, // Azure\n x_EffectivePeriodStart: datetime, // Azure\n x_EffectiveUnitPrice: decimal, // Azure\n x_EffectiveUnitPriceDiscount: decimal, // Hubs add-on\n x_EffectiveUnitPriceDiscountPercent: decimal, // Hubs add-on\n x_IngestionTime: datetime, // Hubs add-on\n x_PricingBlockSize: decimal, // Hubs add-on\n x_PricingCurrency: string, // Azure\n x_PricingSubcategory: string, // Hubs add-on\n x_PricingUnitDescription: string, // Azure\n x_SkuDescription: string, // Azure\n x_SkuId: string, // Azure\n x_SkuIncludedQuantity: decimal, // Azure EA\n x_SkuMeterCategory: string, // Azure\n x_SkuMeterId: string, // Azure\n x_SkuMeterName: string, // Azure\n x_SkuMeterSubcategory: string, // Azure\n x_SkuMeterType: string, // Azure\n x_SkuPriceType: string, // Azure\n x_SkuProductId: string, // Azure\n x_SkuRegion: string, // Azure\n x_SkuServiceFamily: string, // Azure\n x_SkuOfferId: string, // Azure EA\n x_SkuPartNumber: string, // Azure EA\n x_SkuTerm: int, // Azure\n x_SkuTier: decimal, // Azure MCA\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_TotalUnitPriceDiscount: decimal, // Hubs add-on\n x_TotalUnitPriceDiscountPercent: decimal // Hubs add-on\n)\n\n// Update policy for Prices_raw -> Prices_final_v1_0\n.alter table Prices_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Prices_raw\",\n \"Query\": \"Prices_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Cost and usage |=================================================================================================\n// Supported versions:\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All costs transformed to FOCUS 1.0. Use Costs_transform_v1_2() instead.', folder='Costs')\nCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n Costs_raw\n //\n // Change real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n ContractedCost = todecimal(ContractedCost),\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n EffectiveCost = todecimal(EffectiveCost),\n ListCost = todecimal(ListCost),\n ListUnitPrice = todecimal(ListUnitPrice),\n PricingQuantity = todecimal(PricingQuantity),\n UsageAmount = todecimal(UsageAmount),\n UsageQuantity = todecimal(UsageQuantity),\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\n x_Cost = todecimal(x_Cost),\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\n x_OnDemandCost = todecimal(x_OnDemandCost),\n x_OnDemandCostInUsd = todecimal(x_OnDemandCostInUsd),\n x_OnDemandUnitPrice = todecimal(x_OnDemandUnitPrice),\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\n //\n // Dedupe rows\n | extend x_IngestionTime = ingestion_time()\n | extend x_ChargeId = ''\n // TODO: Consider adding a unique charge ID per row\n // hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // // 1. Resource hierarchy (including resource name), highest to lowest\n // BillingAccountId,\n // x_InvoiceSectionId,\n // x_AccountOwnerId,\n // SubAccountId,\n // x_ResourceGroupName,\n // ResourceName,\n // // 2. Resource details\n // ResourceId,\n // RegionId,\n // Tags,\n // CommitmentDiscountId,\n // x_CostCenter,\n // // 4. Meter details\n // SkuPriceId,\n // x_SkuMeterId,\n // x_SkuPartNumber,\n // x_SkuOfferId,\n // x_SkuDetails,\n // // 5. Date\n // ChargePeriodStart\n // ))\n //\n // Identify data quality issues\n | extend x_SourceChanges = trim_end(',', strcat(\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\n 'XEffectiveUnitPriceRoundingError,', ''),\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\n ))\n //\n // Fix columns needed in other changes\n | extend ProviderName = case(\n isnotempty(ProviderName), ProviderName,\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\n ''\n )\n //\n // Identify source\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\n ''\n ))\n // Append version check error code\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\n )\n //\n // Fix quantities\n | extend PricingQuantity = case(\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\n PricingQuantity\n )\n | extend ConsumedQuantity = case(\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, decimal(1)),\n ConsumedQuantity\n )\n //\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\n and (ListUnitPrice == 0 or ContractedUnitPrice == 0)\n and x_EffectiveUnitPrice != 0\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\n | as allCosts\n | where tmp_MissingPrices\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | as costsWithMissingPrices\n | join kind=leftouter (\n Prices_final_v1_0\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\n ) on tmp_ReservationPriceLookupKey\n //\n // Select the best price to use for each row\n // TODO: Save values before changing -- | extend x_old_ContractedUnitPrice = ContractedUnitPrice, x_old_EffectiveUnitPrice = x_EffectiveUnitPrice, x_old_ListUnitPrice = ListUnitPrice, x_old_ListCost = ListCost, x_old_ContractedCost = ContractedCost\n | extend x_EffectiveUnitPrice = case(\n // If price is a rounding error away from the billed price, use the billed price\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\n // If price is a rounding error away from the contracted price, use the contracted price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\n x_EffectiveUnitPrice\n )\n | extend ContractedUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\n x_EffectiveUnitPrice\n )\n | extend ListUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // Otherwise, assume the contracted price is the same as list price to support aggregations\n ContractedUnitPrice\n )\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\n | extend ContractedCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\n // ContractedCost is 0 in all other scenarios...\n // If 0 and there's a billed cost and prices are the same, use BilledCost\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume EffectiveCost\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\n // Fall back to the original value for any unhandled scenarios\n ContractedCost\n )\n | extend ListCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\n // ListCost is 0 in all other scenarios...\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume ContractedCost\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\n // Fall back to the original value for any unhandled scenarios\n ListCost\n )\n // Merge the rest of the unmodified cost records and remove excess columns\n | union (allCosts | where not(tmp_MissingPrices))\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\n //\n // BUG: Fix ContractedCost that has bad values\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\n //\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), todecimal(''))\n | extend ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\n //\n // Convert IDs to lowercase for consistency\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\n //\n // BUG: Remove EffectiveCost for commitment discount purchases\n | extend EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), EffectiveCost)\n | extend x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), x_EffectiveCostInUsd)\n //\n // Clean up resource columns\n | extend ResourceId = case(\n isnotempty(ResourceId), ResourceId,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\n ResourceId)\n | extend ResourceName = tolower(case(\n isnotempty(ResourceName), ResourceName,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\n ResourceName))\n | extend x_ResourceType = case(\n isnotempty(x_ResourceType), x_ResourceType,\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\n x_ResourceType)\n | extend ResourceType = case(\n // Use existing resource type display name unless it's an internal resource type ID\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\n // Use CommitmentDiscountType for commitment discount purchases\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\n // Look up display name from internal type\n isnotempty(x_ResourceType), coalesce(resource_type(x_ResourceType).SingularDisplayName, ResourceType, x_ResourceType),\n ResourceType)\n //\n // Sort columns and apply final transforms\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId = tolower(BillingAccountId),\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEnd),\n BillingPeriodStart = startofmonth(BillingPeriodStart),\n ChargeCategory = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Credit', 'Credit',\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\n ChargeCategory\n ),\n ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass),\n ChargeDescription,\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\n ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency),\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId = tolower(CommitmentDiscountId),\n CommitmentDiscountName,\n CommitmentDiscountStatus = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Used Commitment', 'Used',\n ChargeSubcategory == 'Unused Commitment', 'Unused',\n CommitmentDiscountStatus\n ),\n CommitmentDiscountType,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\n EffectiveCost,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory = case(\n // Handle FOCUS 1.0-preview PricingCategory values\n PricingCategory == 'On-Demand', 'Standard',\n PricingCategory == 'Commitment-Based', 'Committed',\n PricingCategory\n ),\n PricingQuantity,\n PricingUnit,\n ProviderName,\n // Handle missing PublisherName values\n PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, ''),\n // Handle FOCUS 1.0-preview Region column\n RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region)),\n RegionName = coalesce(RegionName, Region),\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SkuId,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType, // Azure 1.0-preview(v1)+\n Tags = parse_json(Tags),\n x_AccountId, // Azure 1.0-preview(v1)+\n x_AccountName, // Azure 1.0-preview(v1)+\n x_AccountOwnerId, // Azure 1.0-preview(v1)+\n x_BilledCostInUsd, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement = case(\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\n ProviderName\n ), // Hubs add-on\n x_BillingAccountId, // Azure 1.0-preview(v1)+\n x_BillingAccountName, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate, // Azure 1.0-preview(v1)+\n x_BillingProfileId, // Azure 1.0-preview(v1)+\n x_BillingProfileName, // Azure 1.0-preview(v1)+\n x_ChargeId, // Azure 1.0-preview(v1) only\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd), // Azure 1.0+\n x_CostAllocationRuleName, // Azure 1.0-preview(v1)+\n x_CostCategories = parse_json(x_CostCategories), // AWS 1.0 (JSON)\n x_CostCenter, // Azure 1.0-preview(v1)+\n x_Credits = parse_json(x_Credits), // GCP Jan 2024\n x_CostType, // GCP Jan 2024\n x_CurrencyConversionRate, // GCP Jun 2024\n x_CustomerId, // Azure 1.0-preview(v1)+\n x_CustomerName, // Azure 1.0-preview(v1)+\n x_Discount = parse_json(x_Discount), // AWS 1.0 (JSON)\n x_EffectiveCostInUsd, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice, // Azure 1.0-preview(v1)+\n x_ExportTime, // GCP Jan 2024\n x_IngestionTime, // Hubs add-on\n x_InvoiceId = coalesce(InvoiceId, x_InvoiceId), // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId = case( // Azure 1.0-preview(v1)+\n x_InvoiceSectionId == '-2', '',\n x_InvoiceSectionId\n ),\n x_InvoiceSectionName = case( // Azure 1.0-preview(v1)+\n x_InvoiceSectionName == 'Unassigned', '',\n x_InvoiceSectionName\n ),\n x_ListCostInUsd, // Azure 1.0-preview(v1)+\n x_Location, // GCP Jan 2024\n x_Operation, // AWS 1.0\n x_PartnerCreditApplied, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate, // Azure 1.0-preview(v1)+\n x_PricingBlockSize, // Azure 1.0-preview(v1)+\n x_PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency), // Azure 1.0-preview(v1)+\n x_PricingSubcategory, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription, // Azure 1.0-preview(v1)+\n x_Project, // GCP Jan 2024\n x_PublisherCategory, // Azure 1.0-preview(v1)+\n x_PublisherId, // Azure 1.0-preview(v1)+\n x_ResellerId, // Azure 1.0-preview(v1)+\n x_ResellerName, // Azure 1.0-preview(v1)+\n x_ResourceGroupName = tolower(x_ResourceGroupName), // Azure 1.0-preview(v1)+\n x_ResourceType, // Azure 1.0-preview(v1)+\n x_ServiceCode, // AWS 1.0\n x_ServiceId, // GCP Jan 2024\n x_ServicePeriodEnd, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart, // Azure 1.0-preview(v1)+\n x_SkuDescription, // Azure 1.0-preview(v1)+\n x_SkuDetails = parse_json(x_SkuDetails), // Azure 1.0-preview(v1)+\n x_SkuIsCreditEligible, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory, // Azure 1.0-preview(v1)+\n x_SkuMeterId, // Azure 1.0-preview(v1)+\n x_SkuMeterName = coalesce(SkuMeter, x_SkuMeterName), // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory, // Azure 1.0-preview(v1)+\n x_SkuOfferId, // Azure 1.0-preview(v1)+\n x_SkuOrderId, // Azure 1.0-preview(v1)+\n x_SkuOrderName, // Azure 1.0-preview(v1)+\n x_SkuPartNumber, // Azure 1.0-preview(v1)+\n x_SkuRegion, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily, // Azure 1.0-preview(v1)+\n x_SkuTerm, // Azure 1.0-preview(v1)+\n x_SkuTier, // Azure 1.0-preview(v1)+\n x_SourceChanges, // Hubs add-on\n x_SourceName, // Hubs add-on\n x_SourceProvider, // Hubs add-on\n x_SourceType, // Hubs add-on\n x_SourceVersion, // Hubs add-on\n x_UsageType // AWS 1.0\n}\n\n// Costs_final_v1_0 table\n.create-merge table Costs_final_v1_0 (\n AvailabilityZone: string,\n BilledCost: decimal,\n BillingAccountId: string,\n BillingAccountName: string,\n BillingAccountType: string, // Azure 1.0-preview(v1)+\n BillingCurrency: string,\n BillingPeriodEnd: datetime,\n BillingPeriodStart: datetime,\n ChargeCategory: string,\n ChargeClass: string,\n ChargeDescription: string,\n ChargeFrequency: string,\n ChargePeriodEnd: datetime,\n ChargePeriodStart: datetime,\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview only\n CommitmentDiscountId: string,\n CommitmentDiscountName: string,\n CommitmentDiscountStatus: string,\n CommitmentDiscountType: string,\n ConsumedQuantity: decimal,\n ConsumedUnit: string,\n ContractedCost: decimal,\n ContractedUnitPrice: decimal,\n EffectiveCost: decimal,\n InvoiceIssuerName: string,\n ListCost: decimal,\n ListUnitPrice: decimal,\n PricingCategory: string,\n PricingQuantity: decimal,\n PricingUnit: string,\n ProviderName: string,\n PublisherName: string,\n RegionId: string,\n RegionName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n ServiceCategory: string,\n ServiceName: string,\n SkuId: string,\n SkuPriceId: string,\n SubAccountId: string,\n SubAccountName: string,\n SubAccountType: string,\n Tags: dynamic,\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_BilledCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: decimal, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: decimal, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_ContractedCostInUsd: decimal, // Azure 1.0+\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_Credits: dynamic, // GCP Jan 2024\n x_CostType: string, // GCP Jan 2024\n x_CurrencyConversionRate: decimal, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: dynamic, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: decimal, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024\n x_IngestionTime: datetime, // Hubs add-on\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_Operation: string, // AWS 1.0\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: decimal, // Azure 1.0-preview(v1)+\n x_PricingCurrency: string, // Azure 1.0-preview(v1)+\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterName: string, // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceChanges: string, // Hubs add-on\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_UsageType: string // AWS 1.0\n)\n\n// Update policy for Costs_raw -> Costs_final_v1_0 table\n.alter table Costs_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Costs_raw\",\n \"Query\": \"Costs_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Actual costs |===================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use ActualCosts_transform_v1_2() instead.', folder='Costs')\nActualCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n // TODO: Transform actual costs to FOCUS 1.0 format\n ActualCosts_raw\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodStart = Date,\n ChargePeriodEnd = Date + 1d,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = '',\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory = '',\n SkuId = '',\n SkuMeter = '',\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = SubscriptionName,\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentName = '',\n x_ComponentType = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel = '',\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for ActualCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": false,\n \"Source\": \"ActualCosts_raw\",\n \"Query\": \"ActualCosts_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Amortized costs |================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use AmortizedCosts_transform_v1_2() instead.', folder='Costs')\nAmortizedCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n // TODO: Transform actual costs to FOCUS 1.0 format\n AmortizedCosts_raw\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodStart = Date,\n ChargePeriodEnd = Date + 1d,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = '',\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory = '',\n SkuId = '',\n SkuMeter = '',\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = SubscriptionName,\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentName = '',\n x_ComponentType = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel = '',\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for AmortizedCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": false,\n \"Source\": \"AmortizedCosts_raw\",\n \"Query\": \"AmortizedCosts_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All commitment discount usage transformed to FOCUS 1.0. This includes reservationdeatils_raw. Use CommitmentDiscountUsage_transform_v1_2() instead.', folder='Commitment discounts')\nCommitmentDiscountUsage_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n CommitmentDiscountUsage_raw\n //\n // Change real to decimal\n | extend\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\n ReservedHours = todecimal(ReservedHours),\n TotalReservedQuantity = todecimal(TotalReservedQuantity),\n UsedHours = todecimal(UsedHours)\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Handle resource columns\n | extend ResourceId = tolower(InstanceId)\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, x_ServiceModel) on x_ResourceType\n //\n // Sort columns and apply final transforms\n | project\n ChargePeriodEnd = UsageDate + 1d,\n ChargePeriodStart = UsageDate,\n CommitmentDiscountCategory = 'Usage',\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\n CommitmentDiscountType = 'Reservation',\n ConsumedQuantity = UsedHours,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SubAccountId,\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\n x_CommitmentDiscountCommittedAmount = ReservedHours,\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\n x_CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\n x_IngestionTime = ingestion_time(),\n x_ResourceGroupName,\n x_ResourceType,\n // x_RowId = hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // CommitmentDiscountId,\n // ResourceId,\n // ChargePeriodStart\n // )),\n x_ServiceModel,\n x_SkuOrderId = ReservationOrderId,\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\n}\n\n// CommitmentDiscountUsage_final_v1_0 table\n.create-merge table CommitmentDiscountUsage_final_v1_0 (\n ChargePeriodEnd: datetime, // Hubs add-on\n ChargePeriodStart: datetime, // MS 2023-03-01\n CommitmentDiscountCategory: string, // Hubs add-on\n CommitmentDiscountId: string, // MS 2023-03-01\n CommitmentDiscountType: string, // Hubs add-on\n ConsumedQuantity: decimal, // MS 2023-03-01\n ProviderName: string, // Hubs add-on\n ResourceId: string, // MS 2023-03-01\n ResourceName: string, // Hubs add-on\n ResourceType: string, // Hubs add-on\n ServiceCategory: string, // Hubs add-on\n ServiceName: string, // Hubs add-on\n SubAccountId: string, // Hubs add-on\n x_CommitmentDiscountCommittedCount: decimal, // MS 2023-03-01\n x_CommitmentDiscountCommittedAmount: decimal, // MS 2023-03-01\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\n x_CommitmentDiscountNormalizedRatio: decimal, // MS 2023-03-01\n x_CommitmentDiscountQuantity: decimal, // MS 2023-03-01\n x_IngestionTime: datetime, // Hubs add-on\n x_ResourceGroupName: string, // Hubs add-on\n x_ResourceType: string, // Hubs add-on\n x_ServiceModel: string, // Hubs add-on\n x_SkuOrderId: string, // MS 2023-03-01\n x_SkuSize: string, // MS 2023-03-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string // Hubs add-on\n)\n\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_0 table\n.alter table CommitmentDiscountUsage_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"CommitmentDiscountUsage_raw\",\n \"Query\": \"CommitmentDiscountUsage_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All recommendations transformed to FOCUS 1.0. Use Recommendations_transform_v1_2() instead.', folder='Recommendations')\nRecommendations_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Recommendations_raw\n //\n // Change real to decimal\n | extend\n CostWithNoReservedInstances = todecimal(CostWithNoReservedInstances),\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\n NetSavings = todecimal(NetSavings),\n RecommendedQuantity = todecimal(RecommendedQuantity),\n RecommendedQuantityNormalized = todecimal(RecommendedQuantityNormalized),\n TotalCostWithReservedInstances = todecimal(TotalCostWithReservedInstances)\n //\n | extend x_IngestionTime = ingestion_time()\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Convert JSON cost columns to decimal\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\n //\n // Build recommendation details\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\n | extend x_RecommendationDetails = case(\n x_SourceType == 'ReservationRecommendations', bag_pack(\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\n 'CommitmentDiscountResourceType', ResourceType,\n 'CommitmentDiscountScope', Scope,\n 'LookbackPeriodDuration', case(\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\n ''\n ),\n 'LookbackPeriodStart', FirstUsageDate,\n 'RecommendedQuantity', RecommendedQuantity,\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\n 'RegionId', Location,\n 'RegionName', RegionName,\n 'SkuMeterId', MeterId,\n 'SkuPriceDetails', SkuProperties,\n 'SkuSize', coalesce(SKU, SkuName),\n 'SkuTerm', isoMonths(Term)\n ),\n dynamic({})\n )\n //\n // Sort columns and apply final transforms\n | extend x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d)\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\n | project\n ProviderName,\n SubAccountId = iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), ''),\n x_IngestionTime,\n x_EffectiveCostAfter = TotalCostWithReservedInstances,\n x_EffectiveCostBefore = CostWithNoReservedInstances,\n x_EffectiveCostSavings = NetSavings,\n x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d),\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n// Recommendations_final_v1_0 table\n.create-merge table Recommendations_final_v1_0 (\n ProviderName: string,\n SubAccountId: string,\n x_IngestionTime: datetime,\n x_EffectiveCostAfter: decimal,\n x_EffectiveCostBefore: decimal,\n x_EffectiveCostSavings: decimal,\n x_RecommendationDate: datetime,\n x_RecommendationDetails: dynamic,\n x_SourceName: string,\n x_SourceProvider: string,\n x_SourceType: string,\n x_SourceVersion: string\n)\n\n// Update policy for Recommendations_raw -> Recommendations_final_v1_0 table\n.alter table Recommendations_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Recommendations_raw\",\n \"Query\": \"Recommendations_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All transactions transformed to FOCUS 1.0. Use Transactions_transform_v1_2() instead.', folder='Transactions')\nTransactions_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Transactions_raw\n //\n // Change real to decimal\n | extend\n Amount = todecimal(Amount),\n MonetaryCommitment = todecimal(MonetaryCommitment),\n Overage = todecimal(Overage),\n Quantity = todecimal(Quantity)\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Handle BillingPeriodStart/End\n | extend BillingMonth = tostring(BillingMonth)\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\n //\n // Sort columns and apply final transforms\n | project\n BilledCost = Amount,\n BillingAccountId = case(\n BillingProfileId startswith '/', BillingProfileId,\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\n ''\n ),\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\n BillingCurrency = Currency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory = case(\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = case(\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\n EventType == 'Refund', 'Correction',\n ''\n ),\n ChargeDescription = Description,\n ChargeFrequency = case(\n BillingFrequency == 'OneTime', 'One-Time',\n BillingFrequency == 'Recurring', 'Recurring',\n BillingFrequency\n ),\n ChargePeriodStart = EventDate,\n PricingQuantity = Quantity,\n PricingUnit = 'Reservations',\n ProviderName,\n RegionId = Region,\n RegionName = Region,\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerEmail,\n x_CostCenter = CostCenter,\n x_InvoiceId = InvoiceId,\n x_InvoiceNumber = Invoice,\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\n x_IngestionTime = ingestion_time(),\n x_MonetaryCommitment = MonetaryCommitment,\n x_Overage = Overage,\n x_PurchasingBillingAccountId = PurchasingEnrollment,\n x_SkuOrderId = ReservationOrderId,\n x_SkuOrderName = ReservationOrderName,\n x_SkuSize = ArmSkuName,\n x_SkuTerm = isoMonths(Term),\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId = PurchasingSubscriptionGuid,\n x_TransactionType = EventType\n}\n\n// Transactions_final_v1_0 table\n.create-merge table Transactions_final_v1_0 (\n BilledCost: decimal, // MS CM EA+MCA 2023-05-01\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n ChargeCategory: string, // Hubs add-on\n ChargeClass: string, // Hubs add-on\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n PricingQuantity: decimal, // MS CM EA+MCA 2023-05-01\n PricingUnit: string, // Hubs add-on\n ProviderName: string, // Hubs add-on\n RegionId: string, // MS CM EA+MCA 2023-05-01\n RegionName: string, // MS CM EA+MCA 2023-05-01\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\n x_AccountName: string, // MS CM EA 2023-05-01\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\n x_CostCenter: string, // MS CM EA 2023-05-01\n x_InvoiceId: string, // MS CM MCA 2023-05-01\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\n x_IngestionTime: datetime, // Hubs add-on\n x_MonetaryCommitment: decimal, // MS CM EA 2023-05-01\n x_Overage: decimal, // MS CM EA 2023-05-01\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\n)\n\n// Update policy for Transactions_raw -> Transactions_final_v1_0 table\n.alter table Transactions_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Transactions_raw\",\n \"Query\": \"Transactions_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n", + "CONFIG": "config", + "HUB_DATA_EXPLORER": "hubDataExplorer", + "HUB_DB": "Hub", + "INGESTION": "ingestion", + "INGESTION_DB": "Ingestion", + "INGESTION_ID_SEPARATOR": "__", + "ftkReleaseUri": "[if(endsWith(variables('finOpsToolkitVersion'), '-dev'), 'https://github.com/microsoft/finops-toolkit/releases/latest/download', format('https://github.com/microsoft/finops-toolkit/releases/download/v{0}', variables('finOpsToolkitVersion')))]", + "useFabric": "[not(empty(parameters('fabricQueryUri')))]", + "useAzure": "[and(not(variables('useFabric')), not(empty(parameters('clusterName'))))]", + "dataExplorerPrivateDnsZoneName": "[replace(format('privatelink.{0}.{1}', parameters('app').hub.location, replace(environment().suffixes.storage, 'core', 'kusto')), '..', '.')]", + "ingestionCapacity": { + "Dev(No SLA)_Standard_E2a_v4": 1, + "Dev(No SLA)_Standard_D11_v2": 1, + "Standard_D11_v2": 2, + "Standard_D12_v2": 4, + "Standard_D13_v2": 8, + "Standard_D14_v2": 16, + "Standard_D16d_v5": 16, + "Standard_D32d_v4": 32, + "Standard_D32d_v5": 32, + "Standard_DS13_v2+1TB_PS": 8, + "Standard_DS13_v2+2TB_PS": 8, + "Standard_DS14_v2+3TB_PS": 16, + "Standard_DS14_v2+4TB_PS": 16, + "Standard_E2a_v4": 2, + "Standard_E2ads_v5": 2, + "Standard_E2d_v4": 2, + "Standard_E2d_v5": 2, + "Standard_E4a_v4": 4, + "Standard_E4ads_v5": 4, + "Standard_E4d_v4": 4, + "Standard_E4d_v5": 4, + "Standard_E8a_v4": 8, + "Standard_E8ads_v5": 8, + "Standard_E8as_v4+1TB_PS": 8, + "Standard_E8as_v4+2TB_PS": 8, + "Standard_E8as_v5+1TB_PS": 8, + "Standard_E8as_v5+2TB_PS": 8, + "Standard_E8d_v4": 8, + "Standard_E8d_v5": 8, + "Standard_E8s_v4+1TB_PS": 8, + "Standard_E8s_v4+2TB_PS": 8, + "Standard_E8s_v5+1TB_PS": 8, + "Standard_E8s_v5+2TB_PS": 8, + "Standard_E16a_v4": 16, + "Standard_E16ads_v5": 16, + "Standard_E16as_v4+3TB_PS": 16, + "Standard_E16as_v4+4TB_PS": 16, + "Standard_E16as_v5+3TB_PS": 16, + "Standard_E16as_v5+4TB_PS": 16, + "Standard_E16d_v4": 16, + "Standard_E16d_v5": 16, + "Standard_E16s_v4+3TB_PS": 16, + "Standard_E16s_v4+4TB_PS": 16, + "Standard_E16s_v5+3TB_PS": 16, + "Standard_E16s_v5+4TB_PS": 16, + "Standard_E64i_v3": 64, + "Standard_E80ids_v4": 80, + "Standard_EC8ads_v5": 8, + "Standard_EC8as_v5+1TB_PS": 8, + "Standard_EC8as_v5+2TB_PS": 8, + "Standard_EC16ads_v5": 16, + "Standard_EC16as_v5+3TB_PS": 16, + "Standard_EC16as_v5+4TB_PS": 16, + "Standard_L4s": 4, + "Standard_L8as_v3": 8, + "Standard_L8s": 8, + "Standard_L8s_v2": 8, + "Standard_L8s_v3": 8, + "Standard_L16as_v3": 16, + "Standard_L16s": 16, + "Standard_L16s_v2": 16, + "Standard_L16s_v3": 16, + "Standard_L32as_v3": 32, + "Standard_L32s_v3": 32 + }, + "dataExplorerIngestionCapacity": "[if(variables('useFabric'), parameters('fabricCapacityUnits'), if(not(variables('useAzure')), 1, coalesce(tryGet(variables('ingestionCapacity'), parameters('clusterSku')), 1)))]", + "dataExplorerUri": "[if(variables('useFabric'), parameters('fabricQueryUri'), format('https://{0}.{1}.kusto.windows.net', replace(parameters('clusterName'), '_', '-'), parameters('app').hub.location))]", + "finOpsToolkitVersion": "12.0" + }, + "resources": { + "cluster::adfClusterAdmin": { + "condition": "[variables('useAzure')]", + "type": "Microsoft.Kusto/clusters/principalAssignments", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), 'adf-mi-cluster-admin')]", + "properties": { + "principalType": "App", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[reference('dataFactory', '2018-06-01', 'full').identity.tenantId]", + "role": "AllDatabasesAdmin" + }, + "dependsOn": [ + "cluster", + "dataFactory" + ] + }, + "cluster::ingestionDb": { + "condition": "[variables('useAzure')]", + "type": "Microsoft.Kusto/clusters/databases", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), variables('INGESTION_DB'))]", + "location": "[parameters('app').hub.location]", + "kind": "ReadWrite", + "dependsOn": [ + "cluster" + ] + }, + "cluster::hubDb": { + "condition": "[variables('useAzure')]", + "type": "Microsoft.Kusto/clusters/databases", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), variables('HUB_DB'))]", + "location": "[parameters('app').hub.location]", + "kind": "ReadWrite", + "dependsOn": [ + "cluster" + ] + }, + "dataFactoryVNet::dataExplorerManagedPrivateEndpoint": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', variables('HUB_DATA_EXPLORER'))]", + "properties": { + "name": "[variables('HUB_DATA_EXPLORER')]", + "groupId": "cluster", + "privateLinkResourceId": "[resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-'))]", + "fqdns": [ + "[format('https://{0}.{1}.kusto.windows.net', replace(parameters('clusterName'), '_', '-'), parameters('app').hub.location)]" + ] + }, + "dependsOn": [ + "appRegistration", + "cluster" + ] + }, + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "dependsOn": [ + "appRegistration" + ] + }, + "blobPrivateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "dependsOn": [ + "appRegistration" + ] + }, + "queuePrivateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.queue.{0}', environment().suffixes.storage)]", + "dependsOn": [ + "appRegistration" + ] + }, + "tablePrivateDnsZone": { + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.table.{0}', environment().suffixes.storage)]", + "dependsOn": [ + "appRegistration" + ] + }, + "storage": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]", + "dependsOn": [ + "appRegistration" + ] + }, + "cluster": { + "condition": "[variables('useAzure')]", + "type": "Microsoft.Kusto/clusters", + "apiVersion": "2023-08-15", + "name": "[replace(parameters('clusterName'), '_', '-')]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Kusto/clusters'), createObject()))]", + "sku": { + "name": "[parameters('clusterSku')]", + "tier": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 'Basic', 'Standard')]", + "capacity": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 1, if(equals(parameters('clusterCapacity'), 1), 2, parameters('clusterCapacity')))]" + }, + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "enableStreamingIngest": true, + "enableAutoStop": false, + "publicNetworkAccess": "[if(parameters('app').hub.options.privateRouting, 'Disabled', 'Enabled')]" + }, + "dependsOn": [ + "appRegistration" + ] + }, + "clusterStorageAccess": { + "condition": "[variables('useAzure')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(replace(parameters('clusterName'), '_', '-'), subscription().id, 'Storage Blob Data Contributor')]", + "properties": { + "description": "Give \"Storage Blob Data Contributor\" to the cluster", + "principalId": "[reference('cluster', '2023-08-15', 'full').identity.principalId]", + "principalType": "ServicePrincipal", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" + }, + "dependsOn": [ + "appRegistration", + "cluster" + ] + }, + "dataExplorerPrivateDnsZone": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[variables('dataExplorerPrivateDnsZoneName')]", + "location": "global", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateDnsZones'), createObject()))]", + "properties": {} + }, + "dataExplorerPrivateDnsZoneLink": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', variables('dataExplorerPrivateDnsZoneName'), format('{0}-link', replace(variables('dataExplorerPrivateDnsZoneName'), '.', '-')))]", + "location": "global", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateDnsZones/virtualNetworkLinks'), createObject()))]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "dataExplorerPrivateDnsZone" + ] + }, + "dataExplorerEndpoint": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', replace(parameters('clusterName'), '_', '-'))]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateEndpoints'), createObject()))]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.dataExplorer]" + }, + "privateLinkServiceConnections": [ + { + "name": "dataExplorerLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-'))]", + "groupIds": [ + "cluster" + ] } - }, + } + ] + }, + "dependsOn": [ + "cluster" + ] + }, + "dataExplorerPrivateDnsZoneGroup": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', replace(parameters('clusterName'), '_', '-')), 'dataExplorer-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ { - "name": "Set Hub Dataset", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "hubDataset", - "value": { - "value": "@if(equals(toLower(variables('exportDatasetType')), 'focuscost'), 'Costs', if(equals(toLower(variables('exportDatasetType')), 'pricesheet'), 'Prices', if(equals(toLower(variables('exportDatasetType')), 'reservationdetails'), 'CommitmentDiscountUsage', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Recommendations', if(equals(toLower(variables('exportDatasetType')), 'reservationtransactions'), 'Transactions', if(equals(toLower(variables('exportDatasetType')), 'actualcost'), 'ActualCosts', if(equals(toLower(variables('exportDatasetType')), 'amortizedcost'), 'AmortizedCosts', toLower(variables('exportDatasetType')))))))))", - "type": "Expression" - } + "name": "privatelink-westus-kusto-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" } }, { - "name": "Set Destination Folder", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check Schema", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Hub Dataset", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "destinationFolder", - "value": { - "value": "@replace(concat(variables('hubDataset'),'/',substring(variables('date'), 0, 4),'/',substring(variables('date'), 4, 2),'/',toLower(variables('scope')), if(equals(variables('hubDataset'), 'Recommendations'), activity('Read Manifest').output.firstRow.exportConfig.exportName, '')),'//','/')", - "type": "Expression" - } + "name": "privatelink-blob-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" } }, { - "name": "For Each Blob", - "description": "Loop thru each exported file listed in the manifest.", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Set Destination Folder", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(variables('hasNoRows'), json('[]'), activity('Read Manifest').output.firstRow.blobs)", - "type": "Expression" - }, - "batchCount": "[if(parameters('enablePublicAccess'), 30, 4)]", - "isSequential": false, - "activities": [ - { - "name": "Execute", - "description": "Run the ingestion ETL pipeline.", - "type": "ExecutePipeline", - "dependsOn": [], - "policy": { - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_ETL_{1}', variables('safeExportContainerName'), variables('safeIngestionContainerName'))]", - "type": "PipelineReference" - }, - "waitOnCompletion": true, - "parameters": { - "blobPath": { - "value": "@item().blobName", - "type": "Expression" - }, - "destinationFolder": { - "value": "@variables('destinationFolder')", - "type": "Expression" - }, - "destinationFile": { - "value": "@last(array(split(replace(replace(item().blobName, '.gz', ''), '.csv', '.parquet'), '/')))", - "type": "Expression" - }, - "ingestionId": { - "value": "@activity('Read Manifest').output.firstRow.runInfo.runId", - "type": "Expression" - }, - "schemaFile": { - "value": "@variables('schemaFile')", - "type": "Expression" - }, - "exportDatasetType": { - "value": "@variables('exportDatasetType')", - "type": "Expression" - }, - "exportDatasetVersion": { - "value": "@variables('exportDatasetVersion')", - "type": "Expression" - } - } - } - } - ] + "name": "privatelink-table-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.table.{0}', environment().suffixes.storage))]" } }, { - "name": "Copy Manifest", - "description": "Copy the manifest to the ingestion container to trigger ADX ingestion", - "type": "Copy", - "dependsOn": [ - { - "activity": "For Each Blob", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "sink": { - "type": "JsonSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "JsonWriteSettings" - } - }, - "enableStaging": false - }, - "inputs": [ - { - "referenceName": "manifest", - "type": "DatasetReference", - "parameters": { - "fileName": "manifest.json", - "folderPath": { - "value": "@pipeline().parameters.folderPath", - "type": "Expression" - } - } - } - ], - "outputs": [ - { - "referenceName": "manifest", - "type": "DatasetReference", - "parameters": { - "fileName": "manifest.json", - "folderPath": { - "value": "[format('@concat(''{0}/'', variables(''destinationFolder''))', parameters('ingestionContainerName'))]", - "type": "Expression" - } - } - } - ] + "name": "privatelink-queue-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" + } } - ], + ] + }, + "dependsOn": [ + "appRegistration", + "dataExplorerEndpoint", + "dataExplorerPrivateDnsZone" + ] + }, + "dataFactoryVNet": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "dependsOn": [ + "appRegistration" + ] + }, + "linkedService_dataExplorer": { + "condition": "[or(variables('useAzure'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('HUB_DATA_EXPLORER'))]", + "properties": "[shallowMerge(createArray(createObject('type', 'AzureDataExplorer', 'parameters', createObject('database', createObject('type', 'String', 'defaultValue', variables('INGESTION_DB'))), 'typeProperties', createObject('endpoint', variables('dataExplorerUri'), 'database', '@{linkedService().database}', 'tenant', reference('dataFactory', '2018-06-01', 'full').identity.tenantId, 'servicePrincipalId', reference('dataFactory', '2018-06-01', 'full').identity.principalId)), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]", + "dependsOn": [ + "appRegistration", + "cluster", + "dataFactory" + ] + }, + "linkedService_ftkRepo": { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ftkRepo')]", + "properties": "[shallowMerge(createArray(createObject('type', 'HttpServer', 'parameters', createObject('filePath', createObject('type', 'string')), 'typeProperties', createObject('url', '@concat(''https://gitapp.hub.com/microsoft/finops-toolkit/'', linkedService().filePath)', 'enableServerCertificateValidation', true(), 'authenticationType', 'Anonymous')), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]", + "dependsOn": [ + "appRegistration" + ] + }, + "dataset_dataExplorer": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('HUB_DATA_EXPLORER'))]", + "properties": { + "type": "AzureDataExplorerTable", + "linkedServiceName": { + "parameters": { + "database": "@dataset().database" + }, + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference" + }, "parameters": { - "folderPath": { - "type": "string" + "database": { + "type": "String", + "defaultValue": "[variables('INGESTION_DB')]" }, + "table": { + "type": "String" + } + }, + "typeProperties": { + "table": { + "value": "@dataset().table", + "type": "Expression" + } + } + }, + "dependsOn": [ + "appRegistration", + "linkedService_dataExplorer" + ] + }, + "dataset_ftkReleaseFile": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ftkReleaseFile')]", + "properties": { + "linkedServiceName": { + "referenceName": "ftkRepo", + "type": "LinkedServiceReference" + }, + "parameters": { "fileName": { "type": "string" + }, + "version": { + "type": "string", + "defaultValue": "[variables('finOpsToolkitVersion')]" } }, - "variables": { - "date": { - "type": "String" - }, - "destinationFolder": { - "type": "String" - }, - "exportDatasetType": { - "type": "String" - }, - "exportDatasetVersion": { - "type": "String" - }, - "hasNoRows": { - "type": "Boolean" - }, - "hubDataset": { - "type": "String" - }, - "mcaColumnToCheck": { - "type": "String" - }, - "schemaFile": { - "type": "String" + "annotations": [], + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "HttpServerLocation", + "relativeUrl": { + "value": "@concat('releases/download/v', dataset().version, '/', dataset().fileName)", + "type": "Expression" + } }, - "scope": { - "type": "String" - } + "columnDelimiter": ",", + "escapeChar": "\\", + "firstRowAsHeader": true, + "quoteChar": "\"" }, - "annotations": [ - "New export" - ] + "schema": [] }, "dependsOn": [ - "dataset_config", - "dataset_manifest", - "dataset_msexports", - "dataset_msexports_gzip", - "dataset_msexports_parquet", - "pipeline_ToIngestion" - ], - "metadata": { - "description": "Queues the msexports_ETL_ingestion pipeline." - } + "appRegistration", + "linkedService_ftkRepo" + ] }, - "pipeline_ToIngestion": { + "pipeline_InitializeHub": { "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ETL_{1}', variables('safeExportContainerName'), variables('safeIngestionContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_InitializeHub', variables('CONFIG')))]", "properties": { "activities": [ { - "name": "Get Existing Parquet Files", - "description": "Get the previously ingested files so we can remove any older data. This is necessary to avoid data duplication in reports.", - "type": "GetMetadata", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[format('{0}_files', variables('safeIngestionContainerName'))]", - "type": "DatasetReference", - "parameters": { - "folderPath": "@pipeline().parameters.destinationFolder" - } - }, - "fieldList": [ - "childItems" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" - } - } - }, - { - "name": "Filter Out Current Exports", - "description": "Remove existing files from the current export so those files do not get deleted.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Get Existing Parquet Files", - "dependencyConditions": [ - "Completed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", - "type": "Expression" - }, - "condition": { - "value": "[format('@and(endswith(item().name, ''.parquet''), not(startswith(item().name, concat(pipeline().parameters.ingestionId, ''{0}''))))', variables('ingestionIdFileNameSeparator'))]", - "type": "Expression" - } - } - }, - { - "name": "Load Schema Mappings", - "description": "Get schema mapping file to use for the CSV to parquet conversion.", + "name": "Get Config", "type": "Lookup", "dependsOn": [], "policy": { - "timeout": "0.12:00:00", - "retry": 0, + "timeout": "0.00:05:00", + "retry": 2, "retryIntervalInSeconds": 30, "secureOutput": false, "secureInput": false @@ -14449,75 +17074,57 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@toLower(pipeline().parameters.schemaFile)", - "type": "Expression" - }, - "folderPath": "[format('{0}/schemas', parameters('configContainerName'))]" - } + "referenceName": "[variables('CONFIG')]", + "type": "DatasetReference" } } }, { - "name": "Failed to Load Schema", - "type": "Fail", + "name": "Set Version", + "type": "SetVariable", "dependsOn": [ { - "activity": "Load Schema Mappings", + "activity": "Get Config", "dependencyConditions": [ - "Failed" + "Succeeded" ] } ], "userProperties": [], "typeProperties": { - "message": { - "value": "@concat('Unable to load the ', pipeline().parameters.schemaFile, ' schema file. Please confirm the schema and version are supported for FinOps hubs ingestion. Unsupported files will remain in the msexports container.')", + "variableName": "version", + "value": { + "value": "@activity('Get Config').output.firstRow.version", "type": "Expression" - }, - "errorCode": "SchemaLoadFailed" + } } }, { - "name": "Set Additional Columns", + "name": "Set Scopes", "type": "SetVariable", "dependsOn": [ { - "activity": "Load Schema Mappings", + "activity": "Get Config", "dependencyConditions": [ "Succeeded" ] } ], - "policy": { - "secureOutput": false, - "secureInput": false - }, "userProperties": [], "typeProperties": { - "variableName": "additionalColumns", + "variableName": "scopes", "value": { - "value": "@intersection(array(json(concat('[{\"name\":\"x_SourceProvider\",\"value\":\"Microsoft\"},{\"name\":\"x_SourceName\",\"value\":\"Cost Management\"},{\"name\":\"x_SourceType\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"},{\"name\":\"x_SourceVersion\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"}'))), activity('Load Schema Mappings').output.firstRow.additionalColumns)", + "value": "@string(activity('Get Config').output.firstRow.scopes)", "type": "Expression" } } }, { - "name": "For Each Old File", - "description": "Loop thru each of the existing files from previous exports.", - "type": "ForEach", + "name": "Set Retention", + "type": "SetVariable", "dependsOn": [ { - "activity": "Convert to Parquet", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Filter Out Current Exports", + "activity": "Get Config", "dependencyConditions": [ "Succeeded" ] @@ -14525,82 +17132,31 @@ ], "userProperties": [], "typeProperties": { - "items": { - "value": "@activity('Filter Out Current Exports').output.Value", - "type": "Expression" - }, - "activities": [ - { - "name": "Delete Old Ingested File", - "description": "Delete the previously ingested files from older exports.", - "type": "Delete", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[variables('safeIngestionContainerName')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@concat(pipeline().parameters.destinationFolder, '/', item().name)", - "type": "Expression" - } - } - }, - "enableLogging": false, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - } - } - } - ] - } - }, - { - "name": "Set Destination Path", - "type": "SetVariable", - "dependsOn": [], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "destinationPath", + "variableName": "retention", "value": { - "value": "[format('@concat(pipeline().parameters.destinationFolder, ''/'', pipeline().parameters.ingestionId, ''{0}'', pipeline().parameters.destinationFile)', variables('ingestionIdFileNameSeparator'))]", + "value": "@string(activity('Get Config').output.firstRow.retention)", "type": "Expression" } } }, { - "name": "Convert to Parquet", - "description": "[format('Convert CSV to parquet and move the file to the {0} container.', parameters('ingestionContainerName'))]", - "type": "Switch", + "name": "Until Capacity Is Available", + "type": "Until", "dependsOn": [ { - "activity": "Set Destination Path", + "activity": "Set Version", "dependencyConditions": [ "Succeeded" ] }, { - "activity": "Load Schema Mappings", + "activity": "Set Scopes", "dependencyConditions": [ "Succeeded" ] }, { - "activity": "Set Additional Columns", + "activity": "Set Retention", "dependencyConditions": [ "Succeeded" ] @@ -14608,406 +17164,374 @@ ], "userProperties": [], "typeProperties": { - "on": { - "value": "@last(array(split(pipeline().parameters.blobPath, '.')))", + "expression": { + "value": "@equals(variables('tryAgain'), false)", "type": "Expression" }, - "cases": [ + "activities": [ { - "value": "csv", - "activities": [ + "name": "Confirm Ingestion Capacity", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", + "commandTimeout": "00:20:00" + }, + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[variables('INGESTION_DB')]" + } + } + }, + { + "name": "If Has Capacity", + "type": "IfCondition", + "dependsOn": [ { - "name": "Convert CSV File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:10:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "activity": "Confirm Ingestion Capacity", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", + "type": "Expression" + }, + "ifFalseActivities": [ + { + "name": "Wait for Ingestion", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 15 + } }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" + { + "name": "Try Again", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait for Ingestion", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": true + } + } + ], + "ifTrueActivities": [ + { + "name": "Set ingestion policy in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": { + "value": "[if(variables('useFabric'), format('.show database {0} policy managed_identity', variables('INGESTION_DB')), format('.alter-merge database {0} policy managed_identity \"[ {{ ''ObjectId'' : ''{1}'', ''AllowedUsages'' : ''NativeIngestion'' }}]\"', variables('INGESTION_DB'), reference('cluster', '2023-08-15', 'full').identity.principalId))]", + "type": "Expression" }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" - } + "commandTimeout": "00:20:00" }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false, - "translator": { - "value": "@activity('Load Schema Mappings').output.firstRow.translator", - "type": "Expression" + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[variables('INGESTION_DB')]" + } } }, - "inputs": [ - { - "referenceName": "[variables('safeExportContainerName')]", - "type": "DatasetReference", + { + "name": "Save Hub Settings in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Set ingestion policy in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": { + "value": "@concat('.append HubSettingsLog <| print version=\"', variables('version'), '\",scopes=dynamic(', variables('scopes'), '),retention=dynamic(', variables('retention'), ') | extend scopes = iff(isnull(scopes[0]), pack_array(scopes), scopes) | mv-apply scopeObj = scopes on (where isnotempty(scopeObj.scope) | summarize scopes = make_set(scopeObj.scope))')", + "type": "Expression" + }, + "commandTimeout": "00:20:00" + }, + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" - } + "database": "[variables('INGESTION_DB')]" } } - ], - "outputs": [ - { - "referenceName": "[variables('safeIngestionContainerName')]", - "type": "DatasetReference", + }, + { + "name": "Update PricingUnits in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Save Hub Settings in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace PricingUnits <| externaldata(x_PricingUnitDescription: string, AccountTypes: string, x_PricingBlockSize: decimal, PricingUnit: string)[@\"{0}/PricingUnits.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away AccountTypes', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" + }, + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" - } + "database": "[variables('INGESTION_DB')]" } } - ] - } - ] - }, - { - "value": "gz", - "activities": [ - { - "name": "Convert GZip CSV File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:10:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" + { + "name": "Update Regions in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update PricingUnits in ADX", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace Regions <| externaldata(ResourceLocation: string, RegionId: string, RegionName: string)[@\"{0}/Regions.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[variables('INGESTION_DB')]" } - }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false, - "translator": { - "value": "@activity('Load Schema Mappings').output.firstRow.translator", - "type": "Expression" } }, - "inputs": [ - { - "referenceName": "[format('{0}_gzip', variables('safeExportContainerName'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" - } + { + "name": "Update ResourceTypes in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update Regions in ADX", + "dependencyConditions": [ + "Succeeded" + ] } - } - ], - "outputs": [ - { - "referenceName": "[variables('safeIngestionContainerName')]", - "type": "DatasetReference", + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace ResourceTypes <| externaldata(x_ResourceType: string, SingularDisplayName: string, PluralDisplayName: string, LowerSingularDisplayName: string, LowerPluralDisplayName: string, IsPreview: bool, Description: string, IconUri: string, Links: string)[@\"{0}/ResourceTypes.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away Links', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" + }, + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" - } + "database": "[variables('INGESTION_DB')]" } } - ] - } - ] - }, - { - "value": "parquet", - "activities": [ - { - "name": "Move Parquet File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "ParquetSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" + { + "name": "Update Services in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update ResourceTypes in ADX", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" - } + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace Services <| externaldata(x_ConsumedService: string, x_ResourceType: string, ServiceName: string, ServiceCategory: string, ServiceSubcategory: string, PublisherName: string, x_PublisherCategory: string, x_Environment: string, x_ServiceModel: string)[@\"{0}/Services.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false - }, - "inputs": [ - { - "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", - "type": "DatasetReference", + "linkedServiceName": { + "referenceName": "[variables('HUB_DATA_EXPLORER')]", + "type": "LinkedServiceReference", "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" - } + "database": "[variables('INGESTION_DB')]" } } - ], - "outputs": [ - { - "referenceName": "[variables('safeIngestionContainerName')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" - } + }, + { + "name": "Ingestion Complete", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Update Services in ADX", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false } - ] - } - ] - } - ], - "defaultActivities": [ - { - "name": "Unsupported File Type", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to ingest the specified export file because the file type is not supported. File: ', pipeline().parameters.blobPath)", - "type": "Expression" - }, - "errorCode": "UnsupportedExportFileType" + } + ] } - } - ] - } - }, - { - "name": "Read Hub Config", - "description": "Read the hub config to determine if the export should be retained.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", - "type": "DatasetReference", - "parameters": { - "fileName": "settings.json", - "folderPath": "[parameters('configContainerName')]" - } - } - } - }, - { - "name": "If Not Retaining Exports", - "description": "If the msexports retention period <= 0, delete the source file. The main reason to keep the source file is to allow for troubleshooting and reprocessing in the future.", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Convert to Parquet", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Read Hub Config", - "dependencyConditions": [ - "Completed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@lessOrEquals(coalesce(activity('Read Hub Config').output.firstRow.retention.msexports.days, 0), 0)", - "type": "Expression" - }, - "ifTrueActivities": [ { - "name": "Delete Source File", - "description": "Delete the exported data file to keep storage costs down. This file is not referenced by any reporting systems.", - "type": "Delete", - "dependsOn": [], + "name": "Abort On Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "If Has Capacity", + "dependencyConditions": [ + "Failed" + ] + } + ], "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, "secureOutput": false, "secureInput": false }, "userProperties": [], "typeProperties": { - "dataset": { - "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" - } - } - }, - "enableLogging": false, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - } + "variableName": "tryAgain", + "value": false } } - ] + ], + "timeout": "0.02:00:00" + } + }, + { + "name": "Timeout Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Until Capacity Is Available", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": "Data Explorer ingestion timed out after 2 hours while waiting for available capacity. Please re-run this pipeline to re-attempt ingestion. If you continue to see this error, please report an issue at https://aka.ms/ftk/ideas.", + "errorCode": "DataExplorerIngestionTimeout" } } ], - "parameters": { - "blobPath": { + "concurrency": 1, + "variables": { + "version": { "type": "String" }, - "destinationFile": { - "type": "string" - }, - "destinationFolder": { - "type": "string" - }, - "ingestionId": { - "type": "string" - }, - "schemaFile": { - "type": "string" - }, - "exportDatasetType": { - "type": "string" - }, - "exportDatasetVersion": { - "type": "string" - } - }, - "variables": { - "additionalColumns": { - "type": "Array" + "scopes": { + "type": "String" }, - "destinationPath": { + "retention": { "type": "String" + }, + "tryAgain": { + "type": "Boolean", + "defaultValue": true } - }, - "annotations": [] + } }, "dependsOn": [ - "dataset_config", - "dataset_ingestion", - "dataset_ingestion_files", - "dataset_msexports", - "dataset_msexports_gzip", - "dataset_msexports_parquet" + "appRegistration", + "cluster", + "linkedService_dataExplorer" ], "metadata": { - "description": "Transforms CSV data to a standard schema and converts to Parquet." + "description": "Initializes the hub instance based on the configuration settings." } }, "pipeline_ToDataExplorer": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "condition": "[or(variables('useAzure'), variables('useFabric'))]", "type": "Microsoft.DataFactory/factories/pipelines", "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ETL_dataExplorer', variables('safeIngestionContainerName')))]", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ETL_dataExplorer', variables('INGESTION')))]", "properties": { "activities": [ { "name": "Read Hub Config", "description": "Read the hub config to determine how long data should be retained.", "type": "Lookup", - "dependsOn": [], "policy": { "timeout": "0.12:00:00", "retry": 0, @@ -15029,11 +17553,11 @@ } }, "dataset": { - "referenceName": "[variables('safeConfigContainerName')]", + "referenceName": "[variables('CONFIG')]", "type": "DatasetReference", "parameters": { "fileName": "settings.json", - "folderPath": "[parameters('configContainerName')]" + "folderPath": "[variables('CONFIG')]" } } } @@ -15098,7 +17622,7 @@ "commandTimeout": "00:20:00" }, "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", + "referenceName": "[variables('HUB_DATA_EXPLORER')]", "type": "LinkedServiceReference" } }, @@ -15172,10 +17696,10 @@ "commandTimeout": "00:20:00" }, "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", + "referenceName": "[variables('HUB_DATA_EXPLORER')]", "type": "LinkedServiceReference", "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "database": "[variables('INGESTION_DB')]" } } }, @@ -15200,16 +17724,16 @@ "userProperties": [], "typeProperties": { "command": { - "value": "[format('@concat(''.ingest into table '', pipeline().parameters.table, '' (\"abfss://{0}@{1}.dfs.{2}/'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.fileName, '';{3}\") with (format=\"parquet\", ingestionMappingReference=\"'', pipeline().parameters.table, ''_mapping\", tags=\"[\\\"drop-by:'', pipeline().parameters.ingestionId, ''\\\", \\\"drop-by:'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.originalFileName, ''\\\", \\\"drop-by:ftk-version-{4}\\\"]\"); print Success = assert(iff(toscalar($command_results | project-keep HasErrors) == false, true, false), \"Ingestion Failed\")'')', parameters('ingestionContainerName'), parameters('storageAccountName'), environment().suffixes.storage, if(variables('useFabric'), 'impersonate', 'managed_identity=system'), variables('ftkVersion'))]", + "value": "[format('@concat(''.ingest into table '', pipeline().parameters.table, '' (\"abfss://{0}@{1}.dfs.{2}/'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.fileName, '';{3}\") with (format=\"parquet\", ingestionMappingReference=\"'', pipeline().parameters.table, ''_mapping\", tags=\"[\\\"drop-by:'', pipeline().parameters.ingestionId, ''\\\", \\\"drop-by:'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.originalFileName, ''\\\", \\\"drop-by:ftk-version-{4}\\\"]\"); print Success = assert(iff(toscalar($command_results | project-keep HasErrors) == false, true, false), \"Ingestion Failed\")'')', variables('INGESTION'), parameters('app').storage, environment().suffixes.storage, if(variables('useFabric'), 'impersonate', 'managed_identity=system'), variables('finOpsToolkitVersion'))]", "type": "Expression" }, "commandTimeout": "01:00:00" }, "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", + "referenceName": "[variables('HUB_DATA_EXPLORER')]", "type": "LinkedServiceReference", "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "database": "[variables('INGESTION_DB')]" } } }, @@ -15240,10 +17764,10 @@ "commandTimeout": "00:20:00" }, "linkedServiceName": { - "referenceName": "[variables('hubDataExplorerName')]", + "referenceName": "[variables('HUB_DATA_EXPLORER')]", "type": "LinkedServiceReference", "parameters": { - "database": "[parameters('dataExplorerIngestionDatabase')]" + "database": "[variables('INGESTION_DB')]" } } }, @@ -15309,648 +17833,1996 @@ "errorCode": "DataExplorerIngestionFailed" } }, - { - "name": "Abort On Pre-Ingest Drop Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Pre-Ingest Cleanup", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } + { + "name": "Abort On Pre-Ingest Drop Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Pre-Ingest Cleanup", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false + } + }, + { + "name": "Pre-Ingest Drop Failed Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Abort On Pre-Ingest Drop Error", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Data Explorer pre-ingestion cleanup (drop extents from raw table) for the ', pipeline().parameters.table, ' table failed. Ingestion was not completed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", + "type": "Expression" + }, + "errorCode": "DataExplorerPreIngestionDropFailed" + } + }, + { + "name": "Abort On Post-Ingest Drop Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Post-Ingest Cleanup", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false + } + }, + { + "name": "Post-Ingest Drop Failed Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Abort On Post-Ingest Drop Error", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Data Explorer post-ingestion cleanup (drop extents from final tables) for the ', replace(pipeline().parameters.table, '_raw', '_final_*'), ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", + "type": "Expression" + }, + "errorCode": "DataExplorerPostIngestionDropFailed" + } + } + ] + } + } + ], + "timeout": "0.02:00:00" + } + } + ], + "parameters": { + "folderPath": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "originalFileName": { + "type": "string" + }, + "ingestionId": { + "type": "string" + }, + "table": { + "type": "string" + } + }, + "variables": { + "tryAgain": { + "type": "Boolean", + "defaultValue": true + }, + "logRetentionDays": { + "type": "Integer", + "defaultValue": 0 + }, + "finalRetentionMonths": { + "type": "Integer", + "defaultValue": 999 + } + }, + "annotations": [] + }, + "dependsOn": [ + "appRegistration", + "linkedService_dataExplorer" + ], + "metadata": { + "description": "Ingests parquet data into an Azure Data Explorer cluster." + } + }, + "pipeline_ExecuteIngestionETL": { + "condition": "[or(variables('useAzure'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ExecuteETL', variables('INGESTION')))]", + "properties": { + "concurrency": 1, + "activities": [ + { + "name": "Wait", + "description": "Files may not be available immediately after being created.", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 60 + } + }, + { + "name": "Set Container Folder Path", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "containerFolderPath", + "value": { + "value": "@join(skip(array(split(pipeline().parameters.folderPath, '/')), 1), '/')", + "type": "Expression" + } + } + }, + { + "name": "Get Existing Parquet Files", + "description": "Get the previously ingested files so we can get file paths.", + "type": "GetMetadata", + "dependsOn": [ + { + "activity": "Set Container Folder Path", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "ingestion_files", + "type": "DatasetReference", + "parameters": { + "folderPath": "@variables('containerFolderPath')" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + } + }, + { + "name": "Filter Out Folders", + "description": "Remove any folders or manifest files.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Get Existing Parquet Files", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", + "type": "Expression" + }, + "condition": { + "value": "@and(equals(item().type, 'File'), not(contains(toLower(item().name), 'manifest.json')))", + "type": "Expression" + } + } + }, + { + "name": "Set Ingestion Timestamp", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "timestamp", + "value": { + "value": "@utcNow()", + "type": "Expression" + } + } + }, + { + "name": "For Each Old File", + "description": "Loop thru each of the existing files.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Filter Out Folders", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Ingestion Timestamp", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "batchCount": "[variables('dataExplorerIngestionCapacity')]", + "items": { + "value": "@activity('Filter Out Folders').output.Value", + "type": "Expression" + }, + "activities": [ + { + "name": "Execute", + "description": "Run the ADX ETL pipeline.", + "type": "ExecutePipeline", + "dependsOn": [], + "policy": { + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_ETL_dataExplorer', variables('INGESTION'))]", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "folderPath": { + "value": "@variables('containerFolderPath')", + "type": "Expression" + }, + "fileName": { + "value": "@item().name", + "type": "Expression" }, - { - "name": "Pre-Ingest Drop Failed Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Abort On Pre-Ingest Drop Error", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Data Explorer pre-ingestion cleanup (drop extents from raw table) for the ', pipeline().parameters.table, ' table failed. Ingestion was not completed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", - "type": "Expression" - }, - "errorCode": "DataExplorerPreIngestionDropFailed" - } + "originalFileName": { + "value": "[format('@last(array(split(item().name, ''{0}'')))', variables('INGESTION_ID_SEPARATOR'))]", + "type": "Expression" }, - { - "name": "Abort On Post-Ingest Drop Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Post-Ingest Cleanup", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } + "ingestionId": { + "value": "[format('@concat(first(array(split(item().name, ''{0}''))), ''_'', variables(''timestamp''))', variables('INGESTION_ID_SEPARATOR'))]", + "type": "Expression" }, - { - "name": "Post-Ingest Drop Failed Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Abort On Post-Ingest Drop Error", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Data Explorer post-ingestion cleanup (drop extents from final tables) for the ', replace(pipeline().parameters.table, '_raw', '_final_*'), ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", - "type": "Expression" - }, - "errorCode": "DataExplorerPostIngestionDropFailed" - } + "table": { + "value": "@concat(first(array(split(variables('containerFolderPath'), '/'))), '_raw')", + "type": "Expression" } - ] + } + } + } + ] + } + }, + { + "name": "If No Files", + "description": "If there are no files found, fail the pipeline.", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Filter Out Folders", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@equals(length(activity('Filter Out Folders').output.Value), 0)", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Files Not Found", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to locate parquet files to ingest from the ', pipeline().parameters.folderPath, ' path. Please confirm the folder path is the full path, including the \"ingestion\" container and not starting with or ending with a slash (\"/\").')", + "type": "Expression" + }, + "errorCode": "IngestionFilesNotFound" + } + } + ] + } + } + ], + "parameters": { + "folderPath": { + "type": "string" + } + }, + "variables": { + "containerFolderPath": { + "type": "string" + }, + "timestamp": { + "type": "string" + } + }, + "annotations": [ + "New ingestion" + ] + }, + "dependsOn": [ + "appRegistration", + "pipeline_ToDataExplorer" + ], + "metadata": { + "description": "Queues the ingestion_ETL_dataExplorer pipeline to account for Data Factory pipeline trigger limits." + } + }, + "appRegistration": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_Register", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "version": { + "value": "[variables('finOpsToolkitVersion')]" + }, + "features": { + "value": [ + "DataFactory", + "Storage" + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "5436870138046688593" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } } } - ], - "timeout": "0.02:00:00" + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } - } - ], - "parameters": { - "folderPath": { - "type": "string" - }, - "fileName": { - "type": "string" - }, - "originalFileName": { - "type": "string" - }, - "ingestionId": { - "type": "string" - }, - "table": { - "type": "string" - } - }, - "variables": { - "tryAgain": { - "type": "Boolean", - "defaultValue": true - }, - "logRetentionDays": { - "type": "Integer", - "defaultValue": 0 }, - "finalRetentionMonths": { - "type": "Integer", - "defaultValue": 999 - } - }, - "annotations": [] - }, - "dependsOn": [ - "dataset_config", - "linkedService_dataExplorer" - ], - "metadata": { - "description": "Ingests parquet data into an Azure Data Explorer cluster." - } - }, - "pipeline_ExecuteIngestionETL": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ExecuteETL', variables('safeIngestionContainerName')))]", - "properties": { - "concurrency": 1, - "activities": [ - { - "name": "Wait", - "description": "Files may not be available immediately after being created.", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 60 + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + }, + "version": { + "type": "string", + "metadata": { + "description": "Required. Version number of the FinOps hub app." + } + }, + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." + } + }, + "storageRoles": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + } + }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } } }, - { - "name": "Set Container Folder Path", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Succeeded" + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0}', parameters('app').id)]", + "version": "[parameters('version')]" + } + }, + "resources": [] + } + }, + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", + "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + }, + "resources": { + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", + "properties": { + "name": "[parameters('app').storage]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "storageAccount" + ] + }, + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", + "properties": { + "name": "[parameters('app').keyVault]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "keyVault" + ] + }, + "dataFactory::managedVirtualNetwork": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "properties": {}, + "dependsOn": [ + "dataFactory" + ] + }, + "dataFactory::managedIntegrationRuntime": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "default", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('app').hub.location]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedVirtualNetwork" + ] + }, + "dataFactory::linkedService_keyVault": { + "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "keyVault" + ] + }, + "dataFactory::linkedService_storageAccount": { + "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "storageAccount" + ] + }, + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false + }, + "dependsOn": [ + "dfsEndpoint" + ] }, - "userProperties": [], - "typeProperties": { - "variableName": "containerFolderPath", - "value": { - "value": "@join(skip(array(split(pipeline().parameters.folderPath, '/')), 1), '/')", - "type": "Expression" + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('app').hub.options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } } - } - }, - { - "name": "Get Existing Parquet Files", - "description": "Get the previously ingested files so we can get file paths.", - "type": "GetMetadata", - "dependsOn": [ - { - "activity": "Set Container Folder Path", - "dependencyConditions": [ - "Succeeded" + }, + "storageRoleAssignments": { + "copy": { + "name": "storageRoleAssignments", + "count": "[length(variables('factoryStorageRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "storageAccount" + ] + }, + "triggerManagerIdentity": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "dependsOn": [ + "dataFactory" + ] + }, + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "triggerManagerIdentity" + ] + }, + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]", + "location": "[parameters('app').hub.location]", + "sku": { + "name": "[parameters('app').hub.options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" + }, + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + }, + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "blob" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('app').hub.options.keyVaultSku]", + "family": "A" + }, + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + } + }, + "dependsOn": [ + "dataFactory" + ] + }, + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('app').keyVault)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.keyVault]" + }, + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + }, + "dependsOn": [ + "keyVault" + ] }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[format('{0}_files', variables('safeIngestionContainerName'))]", - "type": "DatasetReference", + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", "parameters": { - "folderPath": "@variables('containerFolderPath')" + "keyVaultName": { + "value": "[parameters('app').keyVault]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } } }, - "fieldList": [ - "childItems" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" - } - } - }, - { - "name": "Filter Out Folders", - "description": "Remove any folders or manifest files.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Get Existing Parquet Files", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", - "type": "Expression" + "dependsOn": [ + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", + "getStoragePrivateEndpointConnections", + "keyVault" + ] + }, + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } }, - "condition": { - "value": "@and(equals(item().type, 'File'), not(contains(toLower(item().name), 'manifest.json')))", - "type": "Expression" - } - } - }, - { - "name": "Set Ingestion Timestamp", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections", + "keyVault" + ] }, - "userProperties": [], - "typeProperties": { - "variableName": "timestamp", - "value": { - "value": "@utcNow()", - "type": "Expression" - } - } - }, - { - "name": "For Each Old File", - "description": "Loop thru each of the existing files.", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Filter Out Folders", - "dependencyConditions": [ - "Succeeded" - ] + "getStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } }, - { - "activity": "Data Explorer validation", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "batchCount": "[parameters('dataExplorerIngestionCapacity')]", - "items": { - "value": "@activity('Filter Out Folders').output.Value", - "type": "Expression" + "dependsOn": [ + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", + "stopTriggers", + "storageAccount" + ] + }, + "approveStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveStoragePrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } + } }, - "activities": [ - { - "name": "Execute", - "description": "Run the ADX ETL pipeline.", - "type": "ExecutePipeline", - "dependsOn": [], - "policy": { - "secureInput": false + "dependsOn": [ + "getStoragePrivateEndpointConnections", + "storageAccount" + ] + }, + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" }, - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_ETL_dataExplorer', variables('safeIngestionContainerName'))]", - "type": "PipelineReference" + "identityName": { + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('app').dataFactory]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } }, - "waitOnCompletion": true, - "parameters": { - "folderPath": { - "value": "@variables('containerFolderPath')", - "type": "Expression" + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } }, - "fileName": { - "value": "@item().name", - "type": "Expression" + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } }, - "originalFileName": { - "value": "[format('@last(array(split(item().name, ''{0}'')))', variables('ingestionIdFileNameSeparator'))]", - "type": "Expression" + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } }, - "ingestionId": { - "value": "[format('@concat(first(array(split(item().name, ''{0}''))), ''_'', variables(''timestamp''))', variables('ingestionIdFileNameSeparator'))]", - "type": "Expression" + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } }, - "table": { - "value": "@concat(first(array(split(variables('containerFolderPath'), '/'))), '_raw')", - "type": "Expression" + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } } - } - } - ] - } - }, - { - "name": "If No Files", - "description": "If there are no files found, fail the pipeline.", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Filter Out Folders", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@equals(length(activity('Filter Out Folders').output.Value), 0)", - "type": "Expression" - }, - "ifTrueActivities": [ - { - "name": "Files Not Found", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to locate parquet files to ingest from the ', pipeline().parameters.folderPath, ' path. Please confirm the folder path is the full path, including the \"ingestion\" container and not starting with or ending with a slash (\"/\").')", - "type": "Expression" - }, - "errorCode": "IngestionFilesNotFound" - } - } - ] - } - }, - { - "name": "Data Explorer validation", - "description": "If Data Explorer is stopped, start it", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Set Ingestion Timestamp", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "[format('@equals({0}, true)', variables('deployDataExplorer'))]", - "type": "Expression" - }, - "ifTrueActivities": [ - { - "name": "Start ADX Cluster", - "type": "WebActivity", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "method": "POST", - "url": { - "value": "[format('{0}{1}/start?api-version=2024-04-13', environment().resourceManager, resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')))]", - "type": "Expression" + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - "body": "{}", - "authentication": { - "type": "MSI", - "resource": { - "value": "[environment().resourceManager]", - "type": "Expression" + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." } - } - } - }, - { - "name": "Error ADX Start", - "type": "Fail", - "dependsOn": [ - { - "activity": "Start ADX Cluster After Error", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Failed to start the Data Explorer instance. Message: ', activity('Start ADX Cluster After Error').output.error.message)", - "type": "Expression" }, - "errorCode": { - "value": "@activity('Start ADX Cluster After Error').output.error.code", - "type": "Expression" - } - } - }, - { - "name": "Wait ADX Provision State", - "type": "Wait", - "dependsOn": [ - { - "activity": "Start ADX Cluster", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 600 - } - }, - { - "name": "Start ADX Cluster After Error", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Wait ADX Provision State", - "dependencyConditions": [ - "Succeeded" - ] + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "method": "POST", - "url": { - "value": "[format('{0}{1}/start?api-version=2024-04-13', environment().resourceManager, resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')))]", - "type": "Expression", - "body": "{}" + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" }, - "authentication": { - "type": "MSI", - "resource": { - "value": "[environment().resourceManager]", - "type": "Expression" - } + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } + }, + "dependsOn": [ + "appTelemetry", + "dataFactory", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" ] } - } - ], - "parameters": { - "folderPath": { - "type": "string" - } - }, - "variables": { - "containerFolderPath": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - }, - "annotations": [ - "New ingestion" - ] - }, - "dependsOn": [ - "dataset_ingestion_files", - "pipeline_ToDataExplorer" - ], - "metadata": { - "description": "Queues the ingestion_ETL_dataExplorer pipeline to account for Data Factory pipeline trigger limits." - } - }, - "azuretimezones": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "azuretimezones", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('location')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "4022825617953122148" - } - }, - "parameters": { - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." - } - }, - "timezoneobject": { - "type": "object", - "defaultValue": { - "australiaeast": "AUS Eastern Standard Time", - "australiacentral": "AUS Eastern Standard Time", - "australiacentral2": "AUS Eastern Standard Time", - "australiasoutheast": "AUS Eastern Standard Time", - "brazilsouth": "E. South America Standard Time", - "canadacentral": "Central Standard Time", - "canadaeast": "Eastern Standard Time", - "centralindia": "India Standard Time", - "centralus": "Central Standard Time", - "eastasia": "China Standard Time", - "eastus": "Eastern Standard Time", - "eastus2": "Eastern Standard Time", - "francecentral": "W. Europe Standard Time", - "germanynorth": "W. Europe Standard Time", - "germanywestcentral": "W. Europe Standard Time", - "japaneast": "Japan Standard Time", - "japanwest": "Japan Standard Time", - "koreacentral": "Korea Standard Time", - "koreasouth": "Korea Standard Time", - "northcentralus": "Central Standard Time", - "northeurope": "GMT Standard Time", - "norwayeast": "W. Europe Standard Time", - "norwaywest": "W. Europe Standard Time", - "southcentralus": "Central Standard Time", - "southindia": "India Standard Time", - "southeastasia": "Singapore Standard Time", - "switzerlandnorth": "W. Europe Standard Time", - "switzerlandwest": "W. Europe Standard Time", - "uksouth": "GMT Standard Time", - "ukwest": "GMT Standard Time", - "westcentralus": "Central Standard Time", - "westeurope": "W. Europe Standard Time", - "westindia": "India Standard Time", - "westus": "Pacific Standard Time", - "westus2": "Pacific Standard Time" - } - }, - "utchrs": { - "type": "string", - "defaultValue": "[utcNow('hh')]" - }, - "utcmins": { - "type": "string", - "defaultValue": "[utcNow('mm')]" - }, - "utcsecs": { - "type": "string", - "defaultValue": "[utcNow('ss')]" - } - }, - "variables": { - "loc": "[toLower(replace(parameters('location'), ' ', ''))]", - "timezone": "[coalesce(tryGet(parameters('timezoneobject'), variables('loc')), 'Universal Coordinated Time')]" }, - "resources": [], "outputs": { - "AzureRegion": { + "dataFactoryId": { "type": "string", - "value": "[parameters('location')]" + "metadata": { + "description": "Resource ID of the Data Factory instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" }, - "Timezone": { + "keyVaultId": { "type": "string", - "value": "[variables('timezone')]" + "metadata": { + "description": "Resource ID of the Key Vault instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" }, - "UtcHours": { + "storageAccountId": { "type": "string", - "value": "[parameters('utchrs')]" + "metadata": { + "description": "Resource ID of the storage account instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, - "UtcMinutes": { + "principalId": { "type": "string", - "value": "[parameters('utcmins')]" + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" }, - "UtcSeconds": { + "triggerManagerIdentityName": { "type": "string", - "value": "[parameters('utcsecs')]" + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" } } } } }, - "getStoragePrivateEndpointConnections": { - "condition": "[not(parameters('enablePublicAccess'))]", + "ingestion_OpenDataInternalScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "GetStoragePrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionOpenDataInternal", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "storageAccountName": { - "value": "[parameters('storageAccountName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" + }, + "databaseName": { + "value": "[variables('INGESTION_DB')]" + }, + "scripts": { + "value": { + "OpenDataFunctions_resource_type_1": "[variables('$fxv#0')]", + "OpenDataFunctions_resource_type_2": "[variables('$fxv#1')]", + "OpenDataFunctions_resource_type_3": "[variables('$fxv#2')]", + "OpenDataFunctions_resource_type_4": "[variables('$fxv#3')]", + "OpenDataFunctions_resource_type_5": "[variables('$fxv#4')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -15959,72 +19831,97 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "491732910990436410" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. Name of the FinOps hub Data Explorer instance." } }, - "storageAccountName": { + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { "type": "string", + "defaultValue": "[utcNow()]", "metadata": { - "description": "Required. Name of the storage account." + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "storageManagedPrivateEndpoint" + "cluster", + "cluster::ingestionDb" ] }, - "approveStoragePrivateEndpointConnections": { - "condition": "[not(parameters('enablePublicAccess'))]", + "ingestion_InitScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ApproveStoragePrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionInit", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "storageAccountName": { - "value": "[parameters('storageAccountName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + "databaseName": { + "value": "[variables('INGESTION_DB')]" + }, + "scripts": { + "value": { + "openData": "[variables('$fxv#5')]", + "common": "[variables('$fxv#6')]", + "infra": "[variables('$fxv#7')]", + "rawTables": "[replace(variables('$fxv#8'), '$$rawRetentionInDays$$', string(parameters('rawRetentionInDays')))]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -16033,69 +19930,96 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "491732910990436410" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer instance." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." } }, - "storageAccountName": { + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { "type": "string", + "defaultValue": "[utcNow()]", "metadata": { - "description": "Required. Name of the storage account." + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "getStoragePrivateEndpointConnections" + "cluster", + "cluster::ingestionDb", + "ingestion_OpenDataInternalScripts" ] }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", + "ingestion_VersionedScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "GetKeyVaultPrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionVersioned", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "keyVaultName": { - "value": "[parameters('keyVaultName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" + }, + "databaseName": { + "value": "[variables('INGESTION_DB')]" + }, + "scripts": { + "value": { + "v1_0": "[variables('$fxv#9')]", + "v1_2": "[variables('$fxv#10')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -16104,71 +20028,96 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "11127712826844297340" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. Name of the FinOps hub Data Explorer instance." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." } }, - "keyVaultName": { + "forceUpdateTag": { "type": "string", + "defaultValue": "[utcNow()]", "metadata": { - "description": "Required. Name of the KeyVault." + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "keyVaultManagedPrivateEndpoint" + "cluster", + "cluster::ingestionDb", + "ingestion_InitScripts" ] }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", + "hub_InitScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubInit", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "keyVaultName": { - "value": "[parameters('keyVaultName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + "databaseName": { + "value": "[variables('HUB_DB')]" + }, + "scripts": { + "value": { + "common": "[variables('$fxv#11')]", + "openData": "[variables('$fxv#12')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -16177,68 +20126,96 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "11127712826844297340" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. Name of the FinOps hub Data Explorer instance." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." } }, - "keyVaultName": { + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { "type": "string", + "defaultValue": "[utcNow()]", "metadata": { - "description": "Required. Name of the KeyVault." + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "getKeyVaultPrivateEndpointConnections" + "cluster", + "cluster::hubDb", + "ingestion_InitScripts" ] }, - "getDataExplorerPrivateEndpointConnections": { - "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", + "hub_VersionedScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "GetDataExplorerPrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubVersioned", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "dataExplorerName": { - "value": "[parameters('dataExplorerName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" + }, + "databaseName": { + "value": "[variables('HUB_DB')]" + }, + "scripts": { + "value": { + "v1_0": "[variables('$fxv#13')]", + "v1_2": "[variables('$fxv#14')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -16247,71 +20224,96 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9394304748737938982" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. Name of the FinOps hub Data Explorer instance." } }, - "dataExplorerName": { + "databaseName": { "type": "string", "metadata": { - "description": "Required. Name of the ADX cluster." + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "type": "Microsoft.Kusto/clusters/databases/scripts", "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "dataExplorerManagedPrivateEndpoint" + "cluster", + "cluster::hubDb", + "hub_InitScripts", + "ingestion_VersionedScripts" ] }, - "approveDataExplorerPrivateEndpointConnections": { - "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", + "hub_LatestScripts": { + "condition": "[variables('useAzure')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "ApproveDataExplorerPrivateEndpointConnections", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubLatest", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "dataExplorerName": { - "value": "[parameters('dataExplorerName')]" + "clusterName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" + }, + "databaseName": { + "value": "[variables('HUB_DB')]" + }, + "scripts": { + "value": { + "latest": "[variables('$fxv#15')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" }, - "privateEndpointConnections": { - "value": "[reference('getDataExplorerPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { @@ -16320,465 +20322,353 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "9394304748737938982" + "version": "0.39.26.7824", + "templateHash": "1061109683489780517" } }, "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "clusterName": { + "type": "string", "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "description": "Required. Name of the FinOps hub Data Explorer instance." } }, - "dataExplorerName": { + "databaseName": { "type": "string", "metadata": { - "description": "Required. Name of the ADX cluster." + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, "resources": [ { "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "type": "Microsoft.Kusto/clusters/databases/scripts", "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" - } - } + ] } }, "dependsOn": [ - "getDataExplorerPrivateEndpointConnections" + "cluster", + "cluster::hubDb", + "hub_VersionedScripts" ] }, - "deleteOldResources": { + "getDataExplorerPrivateEndpointConnections": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_ADF.DeleteOldResources", + "apiVersion": "2025-04-01", + "name": "GetDataExplorerPrivateEndpointConnections", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('dataFactoryName')]" - } - ] + "dataExplorerName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "18030646605933559953" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, + "dataExplorerName": { + "type": "string", "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the ADX cluster." } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" } } } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "cluster", + "dataFactoryVNet::dataExplorerManagedPrivateEndpoint" + ] + }, + "approveDataExplorerPrivateEndpointConnections": { + "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveDataExplorerPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataExplorerName": { + "value": "[replace(parameters('clusterName'), '_', '-')]" + }, + "privateEndpointConnections": { + "value": "[reference('getDataExplorerPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "18030646605933559953" + } }, "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "identityName": { + "dataExplorerName": { "type": "string", "metadata": { - "description": "Required. Name of the managed identity to create." + "description": "Required. Name of the ADX cluster." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "cluster", + "getDataExplorerPrivateEndpointConnections" + ] + }, + "trigger_IngestionManifestAdded": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Core_IngestionManifestAddedTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('app').dataFactory]" + }, + "triggerName": { + "value": "[format('{0}_ManifestAdded', variables('INGESTION'))]" + }, + "pipelineName": { + "value": "[format('{0}_ExecuteETL', variables('INGESTION'))]" + }, + "pipelineParameters": { + "value": { + "folderPath": "@triggerBody().folderPath" + } + }, + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "storageContainer": { + "value": "[variables('INGESTION')]" + }, + "storagePathEndsWith": { + "value": "manifest.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "14264521107451792604" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." } }, - "scriptName": { + "triggerName": { "type": "string", - "defaultValue": "[deployment().name]", "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + "description": "Required. Name of the Data Factory trigger to create or update." } }, - "scriptContent": { + "storageAccountName": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Optional. Azure storage container to monitor for updates and trigger events for." } }, - "arguments": { + "storageContainer": { "type": "string", "defaultValue": "", "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." + "description": "Optional. Azure storage container to monitor for updates and trigger events for." } }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], + "storagePathStartsWith": { + "type": "string", + "defaultValue": "", "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "storagePathEndsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] + "pipelineName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + } + } + }, + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" + }, + "parameters": "[parameters('pipelineParameters')]" + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + } } - } + ] } }, "dependsOn": [ - "stopTriggers", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" + "appRegistration", + "pipeline_ExecuteIngestionETL" ] }, - "stopTriggers": { + "runInitializationPipeline": { + "condition": "[or(variables('useAzure'), variables('useFabric'))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_ADF.StopTriggers", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.Analytics_InitializeHub", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -16788,33 +20678,17 @@ "app": { "value": "[parameters('app')]" }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" - }, - "scriptContent": { - "value": "[variables('$fxv#1')]" + "dataFactoryInstances": { + "value": [ + "[parameters('app').dataFactory]" + ] }, - "arguments": { - "value": "-Stop" + "identityName": { + "value": "[reference('appRegistration').outputs.triggerManagerIdentityName.value]" }, - "environmentVariables": { + "startPipelines": { "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('dataFactoryName')]" - }, - { - "name": "Triggers", - "value": "[join(variables('allHubTriggers'), '|')]" - } + "[format('{0}_InitializeHub', variables('CONFIG'))]" ] } }, @@ -16825,22 +20699,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" + "version": "0.39.26.7824", + "templateHash": "4749940909471549408" } }, "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "_1.HubProperties": { "type": "object", "properties": { @@ -16901,702 +20764,1090 @@ } }, "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" } } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } }, - "_1.HubRoutingProperties": { - "type": "object", + "dataFactoryInstances": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of Azure Data Factory instances to start triggers for. Can be up to 1 per publisher." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to use when starting the triggers." + } + }, + "startAllTriggers": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Start all triggers for the Data Factory instances. Default: false." + } + }, + "startPipelines": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. List of pipelines to run. Default: [] (no pipelines)." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "uniqueInstances": "[union(filter(parameters('dataFactoryInstances'), lambda('adf', not(empty(lambdaVariables('adf'))))), createArray())]" + }, + "resources": { + "initialize": { + "copy": { + "name": "initialize", + "count": "[length(variables('uniqueInstances'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[if(lessOrEquals(length(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()])), 64), format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), substring(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), 0, 64))]", "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "scriptStorage": { - "type": "string" + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[parameters('identityName')]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[variables('uniqueInstances')[copyIndex()]]" + }, + { + "name": "Pipelines", + "value": "[join(parameters('startPipelines'), '|')]" + }, + { + "name": "StartAllTriggers", + "value": "[string(parameters('startAllTriggers'))]" + } + ] + } }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - "keyVault": { - "type": "string" + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } }, - "scripts": { - "type": "string" + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } }, - "storage": { - "type": "string" + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" }, - "displayName": { - "type": "string" + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" }, - "suffix": { - "type": "string" + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] }, - "tags": { - "type": "object" + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - } - }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" } } } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } } } }, "dependsOn": [ - "triggerManagerIdentity", - "triggerManagerRoleAssignments" + "appRegistration", + "pipeline_InitializeHub" ] + } + }, + "outputs": { + "clusterId": { + "type": "string", + "metadata": { + "description": "The resource ID of the cluster." + }, + "value": "[if(variables('useFabric'), '', resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-')))]" }, - "trigger_ExportManifestAdded": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_ExportManifestAddedTrigger", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('dataFactoryName')]" - }, - "triggerName": { - "value": "[variables('exportManifestAddedTriggerName')]" - }, - "pipelineName": { - "value": "[format('{0}_ExecuteETL', variables('safeExportContainerName'))]" - }, - "pipelineParameters": { - "value": { - "folderPath": "@triggerBody().folderPath", - "fileName": "@triggerBody().fileName" - } - }, - "storageAccountName": { - "value": "[parameters('storageAccountName')]" - }, - "storageContainer": { - "value": "[parameters('exportContainerName')]" - }, - "storagePathEndsWith": { - "value": "manifest.json" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "10717799137710795976" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } - }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." - } - }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." - } - }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." - } - }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." - } - }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." - } - } - }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" - }, - "parameters": "[parameters('pipelineParameters')]" - } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] - } - } - } - ] - } + "principalId": { + "type": "string", + "metadata": { + "description": "The ID of the cluster system assigned managed identity." }, - "dependsOn": [ - "pipeline_ExecuteExportsETL", - "stopTriggers" - ] + "value": "[if(variables('useFabric'), '', reference('cluster', '2023-08-15', 'full').identity.principalId)]" }, - "trigger_IngestionManifestAdded": { - "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_IngestionManifestAddedTrigger", + "clusterName": { + "type": "string", + "metadata": { + "description": "The name of the cluster." + }, + "value": "[if(variables('useFabric'), '', replace(parameters('clusterName'), '_', '-'))]" + }, + "clusterUri": { + "type": "string", + "metadata": { + "description": "The URI of the cluster." + }, + "value": "[variables('dataExplorerUri')]" + }, + "ingestionDbName": { + "type": "string", + "metadata": { + "description": "The name of the database for data ingestion." + }, + "value": "[variables('INGESTION_DB')]" + }, + "hubDbName": { + "type": "string", + "metadata": { + "description": "The name of the database for queries." + }, + "value": "[variables('HUB_DB')]" + }, + "clusterIngestionCapacity": { + "type": "int", + "metadata": { + "description": "Max ingestion capacity of the cluster." + }, + "value": "[variables('dataExplorerIngestionCapacity')]" + } + } + } + }, + "dependsOn": [ + "cmExports", + "core", + "deleteOldResources" + ] + }, + "remoteHub": { + "condition": "[not(empty(parameters('remoteHubStorageKey')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.RemoteHub", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'RemoteHub')]" + }, + "remoteStorageKey": { + "value": "[parameters('remoteHubStorageKey')]" + }, + "remoteHubStorageUri": { + "value": "[parameters('remoteHubStorageUri')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "3199707033377872229" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('dataFactoryName')]" - }, - "triggerName": { - "value": "[variables('ingestionManifestAddedTriggerName')]" - }, - "pipelineName": { - "value": "[format('{0}_ExecuteETL', variables('safeIngestionContainerName'))]" - }, - "pipelineParameters": { - "value": { - "folderPath": "@triggerBody().folderPath" - } - }, - "storageAccountName": { - "value": "[parameters('storageAccountName')]" - }, - "storageContainer": { - "value": "[parameters('ingestionContainerName')]" - }, - "storagePathEndsWith": { - "value": "manifest.json" - } + "name": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "10717799137710795976" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } - }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." - } + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } + "keyVaultSku": { + "type": "string" }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } + "networkAddressPrefix": { + "type": "string" }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." - } + "privateRouting": { + "type": "bool" }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." - } + "publisherIsolation": { + "type": "bool" }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." - } + "storageInfrastructureEncryption": { + "type": "bool" }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." - } + "storageSku": { + "type": "string" } - }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" - }, - "parameters": "[parameters('pipelineParameters')]" - } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] - } - } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } - ] + } } }, - "dependsOn": [ - "pipeline_ExecuteIngestionETL", - "stopTriggers" - ] + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } }, - "trigger_SettingsUpdated": { - "condition": "[parameters('enableManagedExports')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_SettingsUpdatedTrigger", + "_1.HubRoutingProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "networkId": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('dataFactoryName')]" - }, - "triggerName": { - "value": "[variables('updateConfigTriggerName')]" - }, - "pipelineName": { - "value": "[format('{0}_ConfigureExports', variables('safeConfigContainerName'))]" - }, - "pipelineParameters": { - "value": {} - }, - "storageAccountName": { - "value": "[parameters('storageAccountName')]" - }, - "storageContainer": { - "value": "[parameters('configContainerName')]" - }, - "storagePathEndsWith": { - "value": "settings.json" - } + "networkName": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "10717799137710795976" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } + "queue": { + "$ref": "#/definitions/_1.IdNameObject" }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." - } + "dataFactory": { + "type": "string" }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." - } + "keyVault": { + "type": "string" }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." - } + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "privateRoutingForLinkedServices": { + "parameters": [ + { + "$ref": "#/definitions/_1.HubProperties", + "name": "hub" + } + ], + "output": { + "type": "object", + "value": "[if(parameters('hub').options.privateRouting, createObject('connectVia', createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference')), createObject())]" + }, + "metadata": { + "description": "Returns an object that represents the properties needed to enable private routing for linked services. Use property expansion (`...value`) to apply to a linkedServices resource.", + "__bicep_imported_from!": { + "sourceTemplate": "../../fx/hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + }, + "remoteStorageKey": { + "type": "securestring", + "metadata": { + "description": "Required. Create and store a key for a remote storage account." + } + }, + "remoteHubStorageUri": { + "type": "string", + "metadata": { + "description": "Required. Remote storage account for ingestion dataset." + } + }, + "ingestionContainerName": { + "type": "string", + "defaultValue": "ingestion", + "metadata": { + "description": "Optional. Name of the ingestion container. Default: ingestion." + } + } + }, + "variables": { + "storageKeySecretName": "[format('{0}-storage-key', toLower(parameters('app').hub.name))]", + "finOpsToolkitVersion": "12.0" + }, + "resources": { + "dataFactory::linkedService_remoteHubStorage": { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'remoteHubStorage')]", + "properties": "[shallowMerge(createArray(createObject('annotations', createArray(), 'parameters', createObject(), 'type', 'AzureBlobFS', 'typeProperties', createObject('url', parameters('remoteHubStorageUri'), 'accountKey', createObject('type', 'AzureKeyVaultSecret', 'store', createObject('referenceName', parameters('app').keyVault, 'type', 'LinkedServiceReference'), 'secretName', variables('storageKeySecretName')))), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]" + }, + "dataFactory::dataset_ingestion": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('ingestionContainerName'))]", + "properties": { + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." - } - } - }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" - }, - "parameters": "[parameters('pipelineParameters')]" - } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] - } - } + "fileSystem": "[parameters('ingestionContainerName')]" + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "remoteHubStorage", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "dataFactory::linkedService_remoteHubStorage" + ] + }, + "dataFactory::dataset_ingestion_files": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', parameters('ingestionContainerName')))]", + "properties": { + "annotations": [], + "parameters": { + "folderPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileSystem": "[parameters('ingestionContainerName')]", + "folderPath": { + "value": "@dataset().folderPath", + "type": "Expression" } - ] + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "remoteHubStorage", + "type": "LinkedServiceReference" } }, "dependsOn": [ - "pipeline_ConfigureExports", - "stopTriggers" + "dataFactory::linkedService_remoteHubStorage" ] }, - "startTriggers": { + "keyVault": { + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]" + }, + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]" + }, + "appRegistration": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.Core_ADF.StartTriggers", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.RemoteHub_Register", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -17606,34 +21857,14 @@ "app": { "value": "[parameters('app')]" }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" - }, - "scriptContent": { - "value": "[variables('$fxv#2')]" + "version": { + "value": "[variables('finOpsToolkitVersion')]" }, - "environmentVariables": { + "features": { "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('dataFactoryName')]" - }, - { - "name": "Triggers", - "value": "[join(variables('allHubTriggers'), '|')]" - }, - { - "name": "Pipelines", - "value": "[join(createArray(format('{0}_InitializeHub', variables('safeConfigContainerName'))), '|')]" - } + "DataFactory", + "KeyVault", + "Storage" ] } }, @@ -17644,22 +21875,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "588615643779078900" + "version": "0.39.26.7824", + "templateHash": "5436870138046688593" } }, "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "_1.HubProperties": { "type": "object", "properties": { @@ -17778,6 +21998,9 @@ "subnets": { "type": "object", "properties": { + "dataExplorer": { + "type": "string" + }, "dataFactory": { "type": "string" }, @@ -17804,6 +22027,7 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -17834,38 +22058,38 @@ } } }, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, "HubAppProperties": { "type": "object", "properties": { + "id": { + "type": "string" + }, "name": { "type": "string" }, - "displayName": { + "publisher": { + "type": "string" + }, + "suffix": { "type": "string" }, "tags": { "type": "object" }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - } - } - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - }, "dataFactory": { "type": "string" }, @@ -17874,22 +22098,21 @@ }, "storage": { "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -17897,1339 +22120,2522 @@ } } }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." + "description": "Required. FinOps hub app getting deployed." } }, - "identityName": { + "version": { "type": "string", "metadata": { - "description": "Required. Name of the managed identity to create." + "description": "Required. Version number of the FinOps hub app." } }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." } }, - "scriptContent": { + "storageRoles": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + } + }, + "telemetryString": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0}', parameters('app').id)]", + "version": "[parameters('version')]" + } + }, + "resources": [] } }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", + "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + }, + "resources": { + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", + "properties": { + "name": "[parameters('app').storage]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "storageAccount" + ] + }, + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { + "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", + "properties": { + "name": "[parameters('app').keyVault]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "dataFactory::managedVirtualNetwork", + "keyVault" + ] + }, + "dataFactory::managedVirtualNetwork": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", + "properties": {}, + "dependsOn": [ + "dataFactory" + ] + }, + "dataFactory::managedIntegrationRuntime": { + "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "default", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('app').hub.location]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedVirtualNetwork" + ] }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" + "dataFactory::linkedService_keyVault": { + "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "keyVault" + ] }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", + "dataFactory::linkedService_storageAccount": { + "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" + }, + "dependsOn": [ + "dataFactory", + "dataFactory::managedIntegrationRuntime", + "storageAccount" + ] + }, + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] }, "dependsOn": [ - "identity" + "blobEndpoint" ] }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } + ] }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", "dependsOn": [ - "identity", - "identityRoleAssignments" + "dfsEndpoint" ] - } - } - } - }, - "dependsOn": [ - "deleteOldResources", - "pipeline_InitializeHub", - "trigger_DailySchedule", - "trigger_ExportManifestAdded", - "trigger_IngestionManifestAdded", - "trigger_MonthlySchedule", - "trigger_SettingsUpdated", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" - ] - } - }, - "outputs": { - "resourceId": { - "type": "string", - "metadata": { - "description": "The Resource ID of the Data factory." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "The Name of the Azure Data Factory instance." - }, - "value": "[parameters('dataFactoryName')]" - } - } - } - }, - "dependsOn": [ - "cmExports", - "core", - "dataExplorer", - "remoteHub" - ] - }, - "remoteHub": { - "condition": "[not(empty(parameters('remoteHubStorageKey')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.RemoteHub", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[variables('hub')]" - }, - "remoteStorageKey": { - "value": "[parameters('remoteHubStorageKey')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "3708155483370559900" - } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ] + }, + "dependsOn": [ + "dataFactory", + "keyVault" + ] }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('app').hub.routing.networkId]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] }, - "keyVault": { - "type": "string" + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] }, - "scripts": { - "type": "string" + "appTelemetry": { + "condition": "[parameters('app').hub.options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "properties": "[variables('telemetryProps')]" }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('app').dataFactory]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } + } }, - "keyVaultSku": { - "type": "string" + "storageRoleAssignments": { + "copy": { + "name": "storageRoleAssignments", + "count": "[length(variables('factoryStorageRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "storageAccount" + ] }, - "networkAddressPrefix": { - "type": "string" + "triggerManagerIdentity": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", + "location": "[parameters('app').hub.location]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "dependsOn": [ + "dataFactory" + ] }, - "privateRouting": { - "type": "bool" + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory", + "triggerManagerIdentity" + ] }, - "publisherIsolation": { - "type": "bool" + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]", + "location": "[parameters('app').hub.location]", + "sku": { + "name": "[parameters('app').hub.options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" }, - "storageInfrastructureEncryption": { - "type": "bool" + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance properties." - } - }, - "remoteStorageKey": { - "type": "securestring", - "metadata": { - "description": "Required. Create and store a key for a remote storage account." - } - } - }, - "variables": { - "$fxv#0": "12.0" - }, - "resources": { - "appRegistration": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "Microsoft.FinOpsHubs.RemoteHub_Register", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[parameters('hub')]" - }, - "publisher": { - "value": "Microsoft FinOps hubs" - }, - "namespace": { - "value": "Microsoft.FinOpsHubs" - }, - "appName": { - "value": "RemoteHub" - }, - "displayName": { - "value": "FinOps hub remote relay" - }, - "appVersion": { - "value": "[variables('$fxv#0')]" - }, - "features": { - "value": [ - "KeyVault", - "Storage" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "15179190433979236138" - } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "blob" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', parameters('app').storage)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { - "networkId": { - "type": "string" + "subnet": { + "id": "[parameters('app').hub.routing.subnets.storage]" }, - "networkName": { - "type": "string" + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('app').keyVault]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('app').hub.options.keyVaultSku]", + "family": "A" }, - "scriptStorage": { - "type": "string" + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + } + }, + "dependsOn": [ + "dataFactory" + ] + }, + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('app').keyVault)]", + "location": "[parameters('app').hub.location]", + "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('app').hub.routing.subnets.keyVault]" }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "groupIds": [ + "vault" + ] } } + ] + }, + "dependsOn": [ + "keyVault" + ] + }, + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + } }, - "subnets": { - "type": "object", - "properties": { - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } }, - "storage": { - "type": "string" + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" } } } }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." + "dependsOn": [ + "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", + "getStoragePrivateEndpointConnections", + "keyVault" + ] + }, + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "subnets": { - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('app').keyVault]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8053086385192962823" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the KeyVault." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } } - } + }, + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections", + "keyVault" + ] }, - "_1.IdNameObject": { - "type": "object", + "getStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "GetStoragePrivateEndpointConnections", "properties": { - "id": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "name": { - "type": "string" + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + } + } } }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppFeature": { - "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], - "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dependsOn": [ + "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", + "stopTriggers", + "storageAccount" + ] }, - "HubAppProperties": { - "type": "object", + "approveStoragePrivateEndpointConnections": { + "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "ApproveStoragePrivateEndpointConnections", "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "tags": { - "type": "object" + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('app').storage]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } }, - "publisher": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "suffix": { - "type": "string" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "17847147681312340960" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } }, - "tags": { - "type": "object" + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" } } - }, - "hub": { - "$ref": "#/definitions/HubProperties" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" } }, - "metadata": { - "name": "Short name of the FinOps hub app (not including the publisher namespace).", - "displayName": "Display name of the FinOps hub app.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", - "publisher": { - "name": "Fully-qualified namespace of the FinOps hub app publisher.", - "displayName": "Display name of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." - }, - "hub": "FinOps hub instance the app is deployed to.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dependsOn": [ + "getStoragePrivateEndpointConnections", + "storageAccount" + ] }, - "HubProperties": { - "type": "object", + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" + "expressionEvaluationOptions": { + "scope": "inner" }, - "version": { - "type": "string" + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('app').dataFactory]" + } + ] + } }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } }, - "storageInfrastructureEncryption": { - "type": "bool" + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - { - "type": "string", - "name": "resourceType" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - { - "type": "bool", - "nullable": true, - "name": "forceAppTags" + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } - ], - "output": { - "type": "object", - "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "getPublisherTags": { - "parameters": [ - { + "parameters": { + "app": { "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "newApp": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - { + "identityName": { "type": "string", - "name": "publisherDisplayName" + "metadata": { + "description": "Required. Name of the managed identity to create." + } }, - { + "scriptName": { "type": "string", - "name": "publisherName" + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } }, - { + "scriptContent": { "type": "string", - "name": "appPartialName" + "metadata": { + "description": "Required. Name of the deployment script to create." + } }, - { + "arguments": { "type": "string", - "name": "appDisplayName" + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } }, - { - "type": "string", - "name": "version" + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } } - ], - "output": { - "$ref": "#/definitions/HubAppProperties", - "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" }, - "metadata": { - "description": "Creates a new FinOps hub app configuration object.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } + }, + "dependsOn": [ + "appTelemetry", + "dataFactory", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" + ] + } + }, + "outputs": { + "dataFactoryId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Data Factory instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" + }, + "keyVaultId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Key Vault instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" + }, + "storageAccountId": { + "type": "string", + "metadata": { + "description": "Resource ID of the storage account instance used by the FinOps hub app." + }, + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + }, + "triggerManagerIdentityName": { + "type": "string", + "metadata": { + "description": "Name of the managed identity used to create and stop ADF triggers." + }, + "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + } + } + } + } + }, + "keyVault_secret": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "keyVault_secret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vaultName": { + "value": "[parameters('app').keyVault]" + }, + "secretName": { + "value": "[variables('storageKeySecretName')]" + }, + "secretValue": { + "value": "[parameters('remoteStorageKey')]" + }, + "secretExpirationInSeconds": { + "value": 1702648632 + }, + "secretNotBeforeInSeconds": { + "value": 10000 + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "8808837526156050188" + } + }, + "parameters": { + "vaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Key Vault instance." + } + }, + "secretName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Key Vault secret to create or update." + } + }, + "secretValue": { + "type": "securestring", + "metadata": { + "description": "Required. Value of the Key Vault secret." + } + }, + "secretExpirationInSeconds": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "Optional. Value of the Key Vault secret expiration date (exp) property. This is represented as seconds since Jan 1, 1970." + } + }, + "secretNotBeforeInSeconds": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "Optional. Value of the Key Vault secret not before date (nbf) property. This is represented as seconds since Jan 1, 1970." + } + } + }, + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('vaultName'), parameters('secretName'))]", + "properties": { + "attributes": "[union(createObject('enabled', true()), if(lessOrEquals(parameters('secretExpirationInSeconds'), 0), createObject(), createObject('exp', parameters('secretExpirationInSeconds'))), if(lessOrEquals(parameters('secretNotBeforeInSeconds'), 0), createObject(), createObject('nbf', parameters('secretNotBeforeInSeconds'))))]", + "value": "[parameters('secretValue')]" } + } + ], + "outputs": { + "secretName": { + "type": "string", + "metadata": { + "description": "Name of the Key Vault secret." + }, + "value": "[parameters('secretName')]" + } + } + } + } + } + }, + "outputs": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Name of the Key Vault instance." + }, + "value": "[parameters('app').keyVault]" + } + } + } + }, + "dependsOn": [ + "core" + ] + }, + "deleteOldResources": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.DeleteOldResources", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('core').outputs.app.value]" + }, + "identityName": { + "value": "[reference('core').outputs.triggerManagerIdentityName.value]" + }, + "scriptContent": { + "value": "[variables('$fxv#1')]" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[reference('core').outputs.app.value.dataFactory]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - { - "namespace": "_1", - "members": { - "newAppInternal": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "publisherName" - }, - { - "type": "string", - "name": "publisherDisplayName" - }, - { - "type": "string", - "name": "publisherSuffix" - }, - { - "type": "object", - "name": "publisherTags" - }, - { - "type": "string", - "name": "appName" - }, - { - "type": "string", - "name": "appDisplayName" - }, - { - "type": "string", - "name": "version" - } - ], - "output": { - "$ref": "#/definitions/HubAppProperties", - "value": { - "name": "[parameters('appName')]", - "displayName": "[parameters('appDisplayName')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", - "publisher": { - "name": "[parameters('publisherName')]", - "displayName": "[parameters('publisherDisplayName')]", - "suffix": "[parameters('publisherSuffix')]", - "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" - }, - "hub": "[parameters('hub')]", - "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", - "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" - } - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "safeStorageName": { - "parameters": [ - { - "type": "string", - "name": "name" - } - ], - "output": { - "type": "string", - "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" - }, - "metadata": { - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } - ], - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance properties." - } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "publisher": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app publisher." - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } + } + }, + "dependsOn": [ + "core" + ] + }, + "startTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "Microsoft.FinOpsHubs.StartTriggers", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('core').outputs.app.value]" + }, + "dataFactoryInstances": { + "value": [ + "[reference('core').outputs.app.value.dataFactory]", + "[reference('cmExports').outputs.app.value.dataFactory]" + ] + }, + "identityName": { + "value": "[reference('core').outputs.triggerManagerIdentityName.value]" + }, + "startAllTriggers": { + "value": true + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "4749940909471549408" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "namespace": { - "type": "string", - "metadata": { - "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } + "keyVaultSku": { + "type": "string" }, - "appName": { - "type": "string", - "metadata": { - "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } + "networkAddressPrefix": { + "type": "string" }, - "displayName": { - "type": "string", - "metadata": { - "description": "Required. Display name of the FinOps hub app." - } + "privateRouting": { + "type": "bool" }, - "appVersion": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Version number of the FinOps hub app." - } + "publisherIsolation": { + "type": "bool" }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." - } + "storageInfrastructureEncryption": { + "type": "bool" }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } + "storageSku": { + "type": "string" } - }, - "variables": { - "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", - "version": "[parameters('appVersion')]" - } - }, - "resources": [] - } - }, - "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" - }, - "resources": { - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', variables('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] + "queue": { + "$ref": "#/definitions/_1.IdNameObject" }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] + "dataFactory": { + "type": "string" }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" - } - } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] + "keyVault": { + "type": "string" }, - "appTelemetry": { - "condition": "[parameters('hub').options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", - "properties": "[variables('telemetryProps')]" + "scripts": { + "type": "string" }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[variables('app').dataFactory]", - "location": "[variables('app').hub.location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } + }, + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app getting deployed." + } + }, + "dataFactoryInstances": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of Azure Data Factory instances to start triggers for. Can be up to 1 per publisher." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to use when starting the triggers." + } + }, + "startAllTriggers": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Start all triggers for the Data Factory instances. Default: false." + } + }, + "startPipelines": { + "type": "array", + "items": { + "type": "string" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. List of pipelines to run. Default: [] (no pipelines)." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "uniqueInstances": "[union(filter(parameters('dataFactoryInstances'), lambda('adf', not(empty(lambdaVariables('adf'))))), createArray())]" + }, + "resources": { + "initialize": { + "copy": { + "name": "initialize", + "count": "[length(variables('uniqueInstances'))]" + }, + "type": "Microsoft.Resources/deployments", + "apiVersion": "2025-04-01", + "name": "[if(lessOrEquals(length(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()])), 64), format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), substring(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), 0, 64))]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[parameters('identityName')]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[variables('uniqueInstances')[copyIndex()]]" + }, + { + "name": "Pipelines", + "value": "[join(parameters('startPipelines'), '|')]" }, + { + "name": "StartAllTriggers", + "value": "[string(parameters('startAllTriggers'))]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.39.26.7824", + "templateHash": "2597700623393789941" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" + "name": { + "type": "string" + }, + "value": { + "type": "string" } } }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[variables('app').storage]", - "location": "[parameters('hub').location]", - "sku": { - "name": "[parameters('hub').options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" - }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', variables('app').storage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "_1.HubProperties": { + "type": "object", "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" + "id": { + "type": "string" }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "blob" - ] + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" - }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', variables('app').storage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", - "groupIds": [ - "dfs" - ] + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } - ] + } }, - "dependsOn": [ - "storageAccount" - ] + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[variables('app').keyVault]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", + "_1.HubRoutingProperties": { + "type": "object", "properties": { - "sku": { - "name": "[parameters('hub').options.keyVaultSku]", - "family": "A" + "networkId": { + "type": "string" }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" + }, + "subnets": { + "type": "object", + "properties": { + "dataExplorer": { + "type": "string" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } - }, - "dependsOn": [ - "dataFactory" - ] + } }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', variables('app').keyVault)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "HubAppProperties": { + "type": "object", "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.keyVault]" + "id": { + "type": "string" }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] + "name": { + "type": "string" + }, + "publisher": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + } }, - "dependsOn": [ - "keyVault" - ] + "metadata": { + "id": "Fully-qualified name of the publisher and app, separated by a dot.", + "name": "Short name of the FinOps hub app. Last segment of the app ID.", + "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "hub": "FinOps hub instance the app is deployed to.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } }, - "outputs": { + "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "FinOps hub app configuration." - }, - "value": "[variables('app')]" + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - } - } - } - } - }, - "keyVault_secret": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "keyVault_secret", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "vaultName": { - "value": "[reference('appRegistration').outputs.app.value.keyVault]" - }, - "secretName": { - "value": "[format('{0}-storage-key', toLower(reference('appRegistration').outputs.app.value.hub.name))]" - }, - "secretValue": { - "value": "[parameters('remoteStorageKey')]" - }, - "secretExpirationInSeconds": { - "value": 1702648632 - }, - "secretNotBeforeInSeconds": { - "value": 10000 - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.36.177.2456", - "templateHash": "338893459125049689" - } - }, - "parameters": { - "vaultName": { + "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the publisher-specific Key Vault instance." + "description": "Required. Name of the managed identity to create." } }, - "secretName": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Required. Name of the Key Vault secret to create or update." + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." } }, - "secretValue": { - "type": "securestring", + "scriptContent": { + "type": "string", "metadata": { - "description": "Required. Value of the Key Vault secret." + "description": "Required. Name of the deployment script to create." } }, - "secretExpirationInSeconds": { - "type": "int", - "defaultValue": -1, + "arguments": { + "type": "string", + "defaultValue": "", "metadata": { - "description": "Optional. Value of the Key Vault secret expiration date (exp) property. This is represented as seconds since Jan 1, 1970." + "description": "Optional. Additional arguments to pass into the deployment script." } }, - "secretNotBeforeInSeconds": { - "type": "int", - "defaultValue": -1, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], "metadata": { - "description": "Optional. Value of the Key Vault secret not before date (nbf) property. This is represented as seconds since Jan 1, 1970." + "description": "Optional. Environment variables to use for the deployment script." } } }, - "resources": [ - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('vaultName'), parameters('secretName'))]", + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "attributes": "[union(createObject('enabled', true()), if(lessOrEquals(parameters('secretExpirationInSeconds'), 0), createObject(), createObject('exp', parameters('secretExpirationInSeconds'))), if(lessOrEquals(parameters('secretNotBeforeInSeconds'), 0), createObject(), createObject('nbf', parameters('secretNotBeforeInSeconds'))))]", - "value": "[parameters('secretValue')]" - } - } - ], - "outputs": { - "secretName": { - "type": "string", - "metadata": { - "description": "Name of the Key Vault secret." + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" }, - "value": "[parameters('secretName')]" + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } - }, - "dependsOn": [ - "appRegistration" - ] - } - }, - "outputs": { - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Name of the Key Vault instance." - }, - "value": "[reference('appRegistration').outputs.app.value.keyVault]" + } } } } - } + }, + "dependsOn": [ + "cmExports", + "core" + ] } }, "outputs": { @@ -19280,28 +24686,28 @@ "metadata": { "description": "The resource ID of the Data Explorer cluster." }, - "value": "[if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.clusterId.value)]" + "value": "[if(not(variables('useAzureDataExplorer')), '', reference('analytics').outputs.clusterId.value)]" }, "clusterUri": { "type": "string", "metadata": { "description": "The URI of the Data Explorer cluster." }, - "value": "[if(variables('useFabric'), parameters('fabricQueryUri'), if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.clusterUri.value))]" + "value": "[if(variables('useFabric'), parameters('fabricQueryUri'), if(not(variables('useAzureDataExplorer')), '', reference('analytics').outputs.clusterUri.value))]" }, "ingestionDbName": { "type": "string", "metadata": { "description": "The name of the Data Explorer database used for ingesting data." }, - "value": "[if(variables('useFabric'), 'Ingestion', if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.ingestionDbName.value))]" + "value": "[if(or(variables('useFabric'), variables('useAzureDataExplorer')), reference('analytics').outputs.ingestionDbName.value, '')]" }, "hubDbName": { "type": "string", "metadata": { "description": "The name of the Data Explorer database used for querying data." }, - "value": "[if(variables('useFabric'), 'Hub', if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.hubDbName.value))]" + "value": "[if(or(variables('useFabric'), variables('useAzureDataExplorer')), reference('analytics').outputs.hubDbName.value, '')]" }, "managedIdentityId": { "type": "string", @@ -19342,70 +24748,70 @@ "metadata": { "description": "Name of the Data Factory instance." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.dataFactoryName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.dataFactoryName.value]" }, "storageAccountId": { "type": "string", "metadata": { "description": "Resource ID of the deployed storage account." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageAccountId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageAccountId.value]" }, "storageAccountName": { "type": "string", "metadata": { "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageAccountName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageAccountName.value]" }, "storageUrlForPowerBI": { "type": "string", "metadata": { "description": "URL to use when connecting custom Power BI reports to your data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageUrlForPowerBI.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageUrlForPowerBI.value]" }, "clusterId": { "type": "string", "metadata": { "description": "Resource ID of the Data Explorer cluster." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.clusterId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.clusterId.value]" }, "clusterUri": { "type": "string", "metadata": { "description": "URI of the Data Explorer cluster." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.clusterUri.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.clusterUri.value]" }, "ingestionDbName": { "type": "string", "metadata": { "description": "Name of the Data Explorer database used for ingesting data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.ingestionDbName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.ingestionDbName.value]" }, "hubDbName": { "type": "string", "metadata": { "description": "Name of the Data Explorer database used for querying data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.hubDbName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.hubDbName.value]" }, "managedIdentityId": { "type": "string", "metadata": { "description": "Object ID of the Data Factory managed identity. This will be needed when configuring managed exports." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.managedIdentityId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.managedIdentityId.value]" }, "managedIdentityTenantId": { "type": "string", "metadata": { "description": "Azure AD tenant ID. This will be needed when configuring managed exports." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.managedIdentityTenantId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.managedIdentityTenantId.value]" } } } \ No newline at end of file diff --git a/docs/deploy/finops-hub-latest.ui.json b/docs/deploy/finops-hub-latest.ui.json index 16b32e2ef..201aefd47 100644 --- a/docs/deploy/finops-hub-latest.ui.json +++ b/docs/deploy/finops-hub-latest.ui.json @@ -696,6 +696,51 @@ } ], "visible": true + }, + { + "name": "remoteHub", + "type": "Microsoft.Common.Section", + "label": "Remote hub configuration", + "elements": [ + { + "name": "remoteHubIntro", + "type": "Microsoft.Common.TextBlock", + "visible": true, + "options": { + "text": "Configure this hub to send data to a remote FinOps hub in another tenant or subscription. This enables cross-tenant cost management scenarios where a central tenant collects cost data from multiple tenants. Leave these fields empty if this is not a remote hub setup." + } + }, + { + "name": "remoteHubStorageUri", + "type": "Microsoft.Common.TextBox", + "label": "Remote hub storage URI", + "toolTip": "Data Lake storage endpoint from the remote hub storage account. Copy from the storage account Settings > Endpoints > Data Lake storage. Example: https://myremotehub.dfs.core.windows.net/", + "constraints": { + "required": false, + "regex": "^$|^https://.*\\.dfs\\.core\\.windows\\.net/?$", + "validationMessage": "Must be a valid Data Lake storage endpoint URL in the format: https://storageaccount.dfs.core.windows.net/" + }, + "visible": true + }, + { + "name": "remoteHubStorageKey", + "type": "Microsoft.Common.PasswordBox", + "label": { + "password": "Remote hub storage key" + }, + "toolTip": "Storage account access key for the remote hub. Copy from the remote hub storage account Security + networking > Access keys > key1/2 > Key.", + "constraints": { + "required": false, + "regex": "^$|^[A-Za-z0-9+/]{86}==$", + "validationMessage": "Must be a valid storage account access key (base64 encoded, ending with ==)" + }, + "options": { + "hideConfirmation": true + }, + "visible": true + } + ], + "visible": true } ] }, @@ -739,6 +784,8 @@ "ingestionRetentionInMonths": "[steps('retention').storage.ingestionMonths]", "dataExplorerRawRetentionInDays": "[steps('retention').dataExplorer.rawDays]", "dataExplorerFinalRetentionInMonths": "[steps('retention').dataExplorer.finalMonths]", + "remoteHubStorageUri": "[steps('advanced').remoteHub.remoteHubStorageUri]", + "remoteHubStorageKey": "[steps('advanced').remoteHub.remoteHubStorageKey]", "tagsByResource": "[steps('tags').tagsByResource]" } } diff --git a/src/templates/finops-hub/modules/Microsoft.CostManagement/ManagedExports/app.bicep b/src/templates/finops-hub/modules/Microsoft.CostManagement/ManagedExports/app.bicep index 848fc8aac..9d9151ed1 100644 --- a/src/templates/finops-hub/modules/Microsoft.CostManagement/ManagedExports/app.bicep +++ b/src/templates/finops-hub/modules/Microsoft.CostManagement/ManagedExports/app.bicep @@ -496,6 +496,7 @@ resource dataFactory 'Microsoft.DataFactory/factories@2018-06-01' existing = { activity: 'Set Scopes' dependencyConditions: [ 'Succeeded' + 'Failed' ] } { @@ -742,6 +743,7 @@ resource dataFactory 'Microsoft.DataFactory/factories@2018-06-01' existing = { activity: 'Set Scopes' dependencyConditions: [ 'Succeeded' + 'Failed' ] } { @@ -1075,6 +1077,7 @@ resource dataFactory 'Microsoft.DataFactory/factories@2018-06-01' existing = { activity: 'Save Scopes' dependencyConditions: [ 'Succeeded' + 'Failed' ] } { From a824d5da2a1f1cfbb766e33917a38f5e45b7eb1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:50:58 +0000 Subject: [PATCH 3/3] Revert docs/deploy files - only keep source bicep changes Co-authored-by: RolandKrummenacher <1803486+RolandKrummenacher@users.noreply.github.com> --- docs/deploy/finops-hub-12.0.json | 33442 ++++++++++-------------- docs/deploy/finops-hub-12.0.ui.json | 47 - docs/deploy/finops-hub-latest.json | 33442 ++++++++++-------------- docs/deploy/finops-hub-latest.ui.json | 47 - 4 files changed, 28036 insertions(+), 38942 deletions(-) diff --git a/docs/deploy/finops-hub-12.0.json b/docs/deploy/finops-hub-12.0.json index 3a65ba896..4dd0a7f94 100644 --- a/docs/deploy/finops-hub-12.0.json +++ b/docs/deploy/finops-hub-12.0.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8677558940311187779" + "version": "0.36.177.2456", + "templateHash": "1039455044893847676" } }, "parameters": { @@ -233,7 +233,7 @@ "resources": [ { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "hub", "properties": { "expressionEvaluationOptions": { @@ -312,29 +312,43 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "13528846700335310209" + "version": "0.36.177.2456", + "templateHash": "7621563314037220493" } }, "definitions": { "_1.HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -343,24 +357,25 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -446,7 +461,7 @@ "apps": {}, "description": "FinOps hub instance properties.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -482,9 +497,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -511,7 +523,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -519,7 +530,7 @@ }, "description": "FinOps hub private network routing properties.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -538,7 +549,7 @@ "name": "Resource name.", "description": "Resource ID and name.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } } @@ -560,7 +571,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -584,7 +595,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -596,38 +607,54 @@ }, { "type": "string", - "name": "id" + "name": "publisherName" }, { "type": "string", - "name": "name" + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherSuffix" + }, + { + "type": "object", + "name": "publisherTags" }, { "type": "string", - "name": "publisher" + "name": "appName" }, { "type": "string", - "name": "suffix" + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" } ], "output": { "$ref": "#/definitions/_1.HubAppProperties", "value": { - "id": "[parameters('id')]", - "name": "[parameters('name')]", - "publisher": "[parameters('publisher')]", - "suffix": "[parameters('suffix')]", - "tags": "[union(parameters('hub').tags, createObject('ftk-hubapp-publisher', parameters('publisher')))]", + "name": "[parameters('appName')]", + "displayName": "[parameters('appDisplayName')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", + "publisher": { + "name": "[parameters('publisherName')]", + "displayName": "[parameters('publisherDisplayName')]", + "suffix": "[parameters('publisherSuffix')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" + }, "hub": "[parameters('hub')]", - "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('suffix'))), 1)), parameters('suffix')), '--', '-')]", - "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('suffix'))), 1)), parameters('suffix')), '--', '-')]", - "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('suffix')))), parameters('suffix'))]" + "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" } }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -715,7 +742,6 @@ "table": "[if(parameters('enablePublicAccess'), createObject('id', '', 'name', ''), _1.dnsZoneIdName('table'))]" }, "subnets": { - "dataExplorer": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'dataExplorer-subnet'))]", "dataFactory": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'private-endpoint-subnet'))]", "keyVault": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'private-endpoint-subnet'))]", "scripts": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'script-subnet'))]", @@ -729,7 +755,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -746,7 +772,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } } @@ -773,7 +799,7 @@ "metadata": { "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -785,21 +811,33 @@ }, { "type": "string", - "name": "publisher" + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "appPartialName" }, { "type": "string", - "name": "app" + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" } ], "output": { "$ref": "#/definitions/_1.HubAppProperties", - "value": "[_1.newAppInternal(parameters('hub'), format('{0}.{1}', parameters('publisher'), parameters('app')), parameters('app'), parameters('publisher'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisher'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisher'))))]" + "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" }, "metadata": { "description": "Creates a new FinOps hub app configuration object.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -853,7 +891,7 @@ "metadata": { "description": "Creates a new FinOps hub configuration object.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } } @@ -1102,12 +1140,12 @@ }, "variables": { "$fxv#0": "12.0", - "$fxv#1": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\n#\n$adfParams = @{\n ResourceGroupName = $env:DataFactoryResourceGroup\n DataFactoryName = $env:DataFactoryName\n}\n\n# Delete old triggers\n$triggers = Get-AzDataFactoryV2Trigger @adfParams -ErrorAction SilentlyContinue `\n| Where-Object { $_.Name -match '^msexports(_(setup|daily|monthly|extract|FileAdded))?$' }\n$DeploymentScriptOutputs[\"stopTriggers\"] = $triggers | Stop-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\n$DeploymentScriptOutputs[\"deleteTriggers\"] = $triggers | Remove-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\n\n# Delete old pipelines\n$DeploymentScriptOutputs[\"pipelines\"] = Get-AzDataFactoryV2Pipeline @adfParams -ErrorAction SilentlyContinue `\n| Where-Object { $_.Name -match '^(msexports_(backfill|extract|fill|get|run|setup|transform)|config_(BackfillData|ExportData|RunBackfill|RunExports))$' } `\n| Remove-AzDataFactoryV2Pipeline -Force -ErrorAction SilentlyContinue\n", + "$fxv#1": "12.0", "hub": "[__bicep.newHub(parameters('hubName'), parameters('location'), parameters('tags'), parameters('tagsByResource'), parameters('storageSku'), parameters('keyVaultSku'), parameters('enableInfrastructureEncryption'), parameters('enablePublicAccess'), parameters('virtualNetworkAddressPrefix'), parameters('enableDefaultTelemetry'))]", "useFabric": "[not(empty(parameters('fabricQueryUri')))]", - "useAzureDataExplorer": "[and(not(variables('useFabric')), not(empty(parameters('dataExplorerName'))))]", + "deployDataExplorer": "[and(not(variables('useFabric')), not(empty(parameters('dataExplorerName'))))]", "telemetryId": "00f120b5-2007-6120-0000-40b000000000", - "telemetryString": "[join(createArray(if(or(empty(parameters('remoteHubStorageUri')), empty(parameters('remoteHubStorageKey'))), '', 'R'), substring(split(parameters('storageSku'), '_')[1], 0, 1), if(not(variables('useFabric')), '', format('F{0}', parameters('fabricCapacityUnits'))), if(not(variables('useAzureDataExplorer')), '', format('X{0}', substring(parameters('dataExplorerSku'), 0, 1))), if(not(variables('useAzureDataExplorer')), '', replace(replace(replace(replace(replace(replace(replace(replace(split(split(parameters('dataExplorerSku'), 'Standard_')[1], '_')[0], 'C', ''), 'D', ''), 'E', ''), 'L', ''), 'a', ''), 'd', ''), 'i', ''), 's', '')), if(or(not(variables('useAzureDataExplorer')), equals(parameters('dataExplorerCapacity'), 1)), '', format('x{0}', parameters('dataExplorerCapacity'))), if(parameters('enablePublicAccess'), '', 'P')), '')]", + "telemetryString": "[join(createArray(if(or(empty(parameters('remoteHubStorageUri')), empty(parameters('remoteHubStorageKey'))), '', 'R'), substring(split(parameters('storageSku'), '_')[1], 0, 1), if(not(variables('useFabric')), '', format('F{0}', parameters('fabricCapacityUnits'))), if(not(variables('deployDataExplorer')), '', format('X{0}', substring(parameters('dataExplorerSku'), 0, 1))), if(not(variables('deployDataExplorer')), '', replace(replace(replace(replace(replace(replace(replace(replace(split(split(parameters('dataExplorerSku'), 'Standard_')[1], '_')[0], 'C', ''), 'D', ''), 'E', ''), 'L', ''), 'a', ''), 'd', ''), 'i', ''), 's', '')), if(or(not(variables('deployDataExplorer')), equals(parameters('dataExplorerCapacity'), 1)), '', format('x{0}', parameters('dataExplorerCapacity'))), if(parameters('enablePublicAccess'), '', 'P')), '')]", "_1.finOpsToolkitVersion": "12.0" }, "resources": { @@ -1132,33 +1170,18 @@ } } }, - "core": { + "infrastructure": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Infrastructure", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'Core')]" - }, - "scopesToMonitor": { - "value": "[parameters('scopesToMonitor')]" - }, - "msexportRetentionInDays": { - "value": "[parameters('exportRetentionInDays')]" - }, - "ingestionRetentionInMonths": { - "value": "[parameters('ingestionRetentionInMonths')]" - }, - "rawRetentionInDays": { - "value": "[parameters('dataExplorerRawRetentionInDays')]" - }, - "finalRetentionInMonths": { - "value": "[parameters('dataExplorerFinalRetentionInMonths')]" + "hub": { + "value": "[variables('hub')]" } }, "template": { @@ -1168,97 +1191,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "13614615614696914627" + "version": "0.36.177.2456", + "templateHash": "8095489755767003461" } }, "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, "_1.HubRoutingProperties": { "type": "object", "properties": { @@ -1291,9 +1228,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -1320,7 +1254,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -1328,7 +1261,7 @@ }, "description": "FinOps hub private network routing properties.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -1347,11 +1280,11 @@ "name": "Resource name.", "description": "Resource ID and name.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, - "HubAppProperties": { + "HubProperties": { "type": "object", "properties": { "id": { @@ -1360,852 +1293,797 @@ "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "location": { "type": "string" }, "tags": { "type": "object" }, - "dataFactory": { - "type": "string" + "tagsByResource": { + "type": "object" }, - "keyVault": { + "version": { "type": "string" }, - "storage": { - "type": "string" + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } } }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "scopesToMonitor": { - "type": "array", - "metadata": { - "description": "Optional. List of scope IDs to monitor and ingest cost for." - } - }, - "msexportRetentionInDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Optional. Number of days of data to retain in the msexports container. Default: 0." - } - }, - "ingestionRetentionInMonths": { - "type": "int", - "defaultValue": 13, - "metadata": { - "description": "Optional. Number of months of data to retain in the ingestion container. Default: 13." - } - }, - "rawRetentionInDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." + "functions": [ + { + "namespace": "__bicep", + "members": { + "getHubTags": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(parameters('hub').tags, coalesce(tryGet(parameters('hub').tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } } - }, - "finalRetentionInMonths": { - "type": "int", - "defaultValue": 13, + } + ], + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", "metadata": { - "description": "Optional. Number of months of data to retain in the Data Explorer *_final_v* tables. Default: 13." + "description": "Required. FinOps hub instance properties." } } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nWrite-Output \"Updating settings.json file...\"\nWrite-Output \" Storage account: $env:storageAccountName\"\nWrite-Output \" Container: $env:containerName\"\n\n$validateScopes = { $_.Length -gt 45 }\n\n# Initialize variables\n$fileName = 'settings.json'\n$filePath = Join-Path -Path . -ChildPath $fileName\n$newScopes = $env:scopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Download existing settings, if they exist\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\nif ($blob)\n{\n $text = Get-Content $filePath -Raw\n Write-Output \"---------\"\n Write-Output $text\n Write-Output \"---------\"\n $json = $text | ConvertFrom-Json\n Write-Output \"Existing settings.json file found. Updating...\"\n\n # Rename exportScopes to scopes + convert to object array\n if ($json.exportScopes)\n {\n Write-Output \" Updating exportScopes...\"\n if ($json.exportScopes[0] -is [string])\n {\n Write-Output \" Converting string array to object array...\"\n $json.exportScopes = @($json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } })\n if (-not ($json.exportScopes -is [array]))\n {\n Write-Output \" Converting single object to object array...\"\n $json.exportScopes = @($json.exportScopes)\n }\n }\n\n Write-Output \" Renaming to 'scopes'...\"\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\n $json.PSObject.Properties.Remove('exportScopes')\n }\n\n # Force string array to object array with unique values\n if ($json.scopes)\n {\n Write-Output \" Converting string array to object array...\"\n $scopeArray = @()\n $json.scopes | Where-Object $validateScopes | ForEach-Object { $scopeArray += $_ } | Select-Object -Unique\n $json.scopes = @() + $scopeArray\n }\n}\n\n# Set default if not found\nif (!$json)\n{\n Write-Output \"No existing settings.json file found. Creating new file...\"\n $json = [ordered]@{\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\n type = 'HubInstance'\n version = ''\n learnMore = 'https://aka.ms/finops/hubs'\n scopes = @()\n retention = @{\n 'msexports' = @{\n days = 0\n }\n 'ingestion' = @{\n months = 13\n }\n 'raw' = @{\n days = 0\n }\n 'final' = @{\n months = 13\n }\n }\n }\n\n $text = $json | ConvertTo-Json\n Write-Output \"---------\"\n Write-Output $text\n Write-Output \"---------\"\n}\n\n# Set default retention\nif (!($json.retention))\n{\n # In case the retention object is not present in the settings.json file (versions before 0.4), add it with default values\n $retention = @\"\n {\n \"msexports\": {\n \"days\": 0\n },\n \"ingestion\": {\n \"months\": 13\n },\n \"raw\": {\n \"days\": 0\n },\n \"final\": {\n \"months\": 13\n }\n }\n\"@\n $json | Add-Member -Name retention -Value (ConvertFrom-Json $retention) -MemberType NoteProperty\n}\n\n# Set or update msexports retention\nif (!($json.retention.msexports))\n{\n $json.retention | Add-Member -Name msexports -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:msexportRetentionInDays)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.msexports.days = [Int32]::Parse($env:msexportRetentionInDays)\n}\n\n# Set or update ingestion retention\nif (!($json.retention.ingestion))\n{\n $json.retention | Add-Member -Name ingestion -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:ingestionRetentionInMonths)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.ingestion.months = [Int32]::Parse($env:ingestionRetentionInMonths)\n}\n\n# Set or update raw retention\nif (!($json.retention.raw))\n{\n $json.retention | Add-Member -Name raw -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:rawRetentionInDays)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.raw.days = [Int32]::Parse($env:rawRetentionInDays)\n}\n\n# Set or update final retention\nif (!($json.retention.final))\n{\n $json.retention | Add-Member -Name final -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:finalRetentionInMonths)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.final.months = [Int32]::Parse($env:finalRetentionInMonths)\n}\n\n# Updating settings\nWrite-Output \"Updating version to $env:ftkVersion...\"\n$json.version = $env:ftkVersion\n$json.scopes = (@() + $json.scopes + $newScopes) | Select-Object -Unique\nif ($null -eq $json.scopes) { $json.scopes = @() }\n$text = $json | ConvertTo-Json\nWrite-Output \"---------\"\nWrite-Output $text\nWrite-Output \"---------\"\n$text | Out-File $filePath\n\n# Upload new/updated settings\nWrite-Output \"Uploading settings.json file...\"\nSet-AzStorageBlobContent @storageContext -File $filePath -Force | Out-Null\n", - "CONFIG": "config", - "INGESTION": "ingestion", - "finOpsToolkitVersion": "12.0" + "nsgName": "[format('{0}-nsg', parameters('hub').routing.networkName)]", + "finopsHubSubnetName": "private-endpoint-subnet", + "scriptSubnetName": "script-subnet", + "dataExplorerSubnetName": "dataExplorer-subnet", + "subnets": "[if(not(parameters('hub').options.privateRouting), createArray(), createArray(createObject('name', variables('finopsHubSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 0), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('scriptSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'delegations', createArray(createObject('name', 'Microsoft.ContainerInstance/containerGroups', 'properties', createObject('serviceName', 'Microsoft.ContainerInstance/containerGroups'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('dataExplorerSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 27, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName')))))))]" }, "resources": { - "dataFactory::dataset_config": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]", + "vNet::finopsHubSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('finopsHubSubnetName'))]", + "dependsOn": [ + "vNet" + ] + }, + "vNet::scriptSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('scriptSubnetName'))]", + "dependsOn": [ + "vNet" + ] + }, + "vNet::dataExplorerSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('dataExplorerSubnetName'))]", + "dependsOn": [ + "vNet" + ] + }, + "blobPrivateDnsZone::blobPrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.blob.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.blob.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "linkedServiceName": { - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - }, - "type": "Json", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().fileName}", - "type": "Expression" - }, - "folderPath": { - "value": "@{dataset().folderPath}", - "type": "Expression" - } - } - }, - "parameters": { - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[reference('configContainer').outputs.containerName.value]" - } + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" } }, "dependsOn": [ - "appRegistration", - "configContainer" + "blobPrivateDnsZone" ] }, - "dataFactory::dataset_ingestion": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('INGESTION'))]", + "dfsPrivateDnsZone::dfsPrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.dfs.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.dfs.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" - }, - "fileSystem": "[reference('ingestionContainer').outputs.containerName.value]" - } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" } }, "dependsOn": [ - "appRegistration", - "ingestionContainer" + "dfsPrivateDnsZone" ] }, - "dataFactory::dataset_ingestion_files": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', variables('INGESTION')))]", + "queuePrivateDnsZone::queuePrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.queue.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.queue.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "annotations": [], - "parameters": { - "folderPath": { - "type": "String" + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + } + }, + "dependsOn": [ + "queuePrivateDnsZone" + ] + }, + "tablePrivateDnsZone::tablePrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.table.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.table.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + } + }, + "dependsOn": [ + "tablePrivateDnsZone" + ] + }, + "scriptEndpoint::scriptPrivateDnsZoneGroup": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', string(parameters('hub').routing.scriptStorage)), 'blob-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', string(parameters('hub').routing.dnsZones.blob.name))]" + } } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileSystem": "[reference('ingestionContainer').outputs.containerName.value]", - "folderPath": { - "value": "@dataset().folderPath", - "type": "Expression" + ] + }, + "dependsOn": [ + "blobPrivateDnsZone", + "scriptEndpoint" + ] + }, + "nsg": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2023-11-01", + "name": "[variables('nsgName')]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/networkSecurityGroups')]", + "properties": { + "securityRules": [ + { + "name": "AllowVnetInBound", + "properties": { + "priority": 100, + "direction": "Inbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "VirtualNetwork", + "destinationAddressPrefix": "VirtualNetwork" + } + }, + { + "name": "AllowAzureLoadBalancerInBound", + "properties": { + "priority": 200, + "direction": "Inbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "AzureLoadBalancer", + "destinationAddressPrefix": "*" + } + }, + { + "name": "DenyAllInBound", + "properties": { + "priority": 4096, + "direction": "Inbound", + "access": "Deny", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "AllowVnetOutBound", + "properties": { + "priority": 100, + "direction": "Outbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "VirtualNetwork", + "destinationAddressPrefix": "VirtualNetwork" + } + }, + { + "name": "AllowInternetOutBound", + "properties": { + "priority": 200, + "direction": "Outbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "Internet" + } + }, + { + "name": "DenyAllOutBound", + "properties": { + "priority": 4096, + "direction": "Outbound", + "access": "Deny", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "*" } } + ] + } + }, + "vNet": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2023-11-01", + "name": "[parameters('hub').routing.networkName]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/virtualNetworks')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[parameters('hub').options.networkAddressPrefix]" + ] }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - } + "subnets": "[variables('subnets')]" }, "dependsOn": [ - "appRegistration", - "ingestionContainer" + "nsg" ] }, - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", + "blobPrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, "dependsOn": [ - "appRegistration" + "vNet" ] }, - "infrastructure": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core_Infrastructure", + "dfsPrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.dfs.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] + }, + "queuePrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.queue.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] + }, + "tablePrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.table.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] + }, + "scriptStorageAccount": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[string(parameters('hub').routing.scriptStorage)]", + "location": "[parameters('hub').location]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/storageAccounts')]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "supportsHttpsTrafficOnly": true, + "allowSharedKeyAccess": true, + "isHnsEnabled": false, + "minimumTlsVersion": "TLS1_2", + "allowBlobPublicAccess": false, + "publicNetworkAccess": "Enabled", + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "id": "[parameters('hub').routing.subnets.scripts]", + "action": "Allow" + } + ] + } + }, + "dependsOn": [ + "vNet::scriptSubnet" + ] + }, + "scriptEndpoint": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', string(parameters('hub').routing.scriptStorage))]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('hub').routing.subnets.storage]" }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[parameters('app').hub]" + "privateLinkServiceConnections": [ + { + "name": "scriptLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', string(parameters('hub').routing.scriptStorage))]", + "groupIds": [ + "blob" + ] + } } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5983517136492624194" + ] + }, + "dependsOn": [ + "scriptStorageAccount", + "vNet::scriptSubnet" + ] + } + }, + "outputs": { + "config": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "FinOps hub configuration settings." + }, + "value": "[parameters('hub')]" + }, + "vNetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the virtual network." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks', parameters('hub').routing.networkName))]" + }, + "vNetAddressSpace": { + "type": "array", + "metadata": { + "description": "Virtual network address prefixes." + }, + "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').addressSpace.addressPrefixes)]" + }, + "vNetSubnets": { + "type": "array", + "metadata": { + "description": "Virtual network subnets." + }, + "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').subnets)]" + }, + "finopsHubSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the FinOps hub network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('finopsHubSubnetName')))]" + }, + "scriptSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the script storage account network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('scriptSubnetName')))]" + }, + "dataExplorerSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Data Explorer network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('dataExplorerSubnetName')))]" + } + } + } + } + }, + "core": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "hub": { + "value": "[variables('hub')]" + }, + "telemetryString": { + "value": "[variables('telemetryString')]" + }, + "scopesToMonitor": { + "value": "[parameters('scopesToMonitor')]" + }, + "msexportRetentionInDays": { + "value": "[parameters('exportRetentionInDays')]" + }, + "ingestionRetentionInMonths": { + "value": "[parameters('ingestionRetentionInMonths')]" + }, + "rawRetentionInDays": { + "value": "[parameters('dataExplorerRawRetentionInDays')]" + }, + "finalRetentionInMonths": { + "value": "[parameters('dataExplorerFinalRetentionInMonths')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "15396896523851766061" + } + }, + "definitions": { + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } + "keyVault": { + "type": "string" }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getHubTags": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(parameters('hub').tags, coalesce(tryGet(parameters('hub').tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance properties." - } - } - }, - "variables": { - "nsgName": "[format('{0}-nsg', parameters('hub').routing.networkName)]", - "finopsHubSubnetName": "private-endpoint-subnet", - "scriptSubnetName": "script-subnet", - "dataExplorerSubnetName": "dataExplorer-subnet", - "subnets": "[if(not(parameters('hub').options.privateRouting), createArray(), createArray(createObject('name', variables('finopsHubSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 0), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('scriptSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'delegations', createArray(createObject('name', 'Microsoft.ContainerInstance/containerGroups', 'properties', createObject('serviceName', 'Microsoft.ContainerInstance/containerGroups'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('dataExplorerSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 27, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName')))))))]" - }, - "resources": { - "vNet::finopsHubSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('finopsHubSubnetName'))]", - "dependsOn": [ - "vNet" - ] - }, - "vNet::scriptSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('scriptSubnetName'))]", - "dependsOn": [ - "vNet" - ] - }, - "vNet::dataExplorerSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('dataExplorerSubnetName'))]", - "dependsOn": [ - "vNet" - ] + "scripts": { + "type": "string" }, - "blobPrivateDnsZone::blobPrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.blob.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.blob.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "blobPrivateDnsZone" - ] + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "dfsPrivateDnsZone::dfsPrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.dfs.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.dfs.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "dfsPrivateDnsZone" - ] + "keyVaultSku": { + "type": "string" }, - "queuePrivateDnsZone::queuePrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.queue.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.queue.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "queuePrivateDnsZone" - ] + "networkAddressPrefix": { + "type": "string" }, - "tablePrivateDnsZone::tablePrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.table.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.table.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "tablePrivateDnsZone" - ] + "privateRouting": { + "type": "bool" }, - "scriptEndpoint::scriptPrivateDnsZoneGroup": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('hub').routing.scriptStorage), 'blob-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', string(parameters('hub').routing.dnsZones.blob.name))]" - } - } - ] - }, - "dependsOn": [ - "blobPrivateDnsZone", - "scriptEndpoint" - ] + "publisherIsolation": { + "type": "bool" }, - "nsg": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2023-11-01", - "name": "[variables('nsgName')]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/networkSecurityGroups')]", - "properties": { - "securityRules": [ - { - "name": "AllowVnetInBound", - "properties": { - "priority": 100, - "direction": "Inbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "VirtualNetwork", - "destinationAddressPrefix": "VirtualNetwork" - } - }, - { - "name": "AllowAzureLoadBalancerInBound", - "properties": { - "priority": 200, - "direction": "Inbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "AzureLoadBalancer", - "destinationAddressPrefix": "*" - } - }, - { - "name": "DenyAllInBound", - "properties": { - "priority": 4096, - "direction": "Inbound", - "access": "Deny", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*" - } - }, - { - "name": "AllowVnetOutBound", - "properties": { - "priority": 100, - "direction": "Outbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "VirtualNetwork", - "destinationAddressPrefix": "VirtualNetwork" - } - }, - { - "name": "AllowInternetOutBound", - "properties": { - "priority": 200, - "direction": "Outbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "Internet" - } - }, - { - "name": "DenyAllOutBound", - "properties": { - "priority": 4096, - "direction": "Outbound", - "access": "Deny", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*" - } - } - ] - } + "storageInfrastructureEncryption": { + "type": "bool" }, - "vNet": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2023-11-01", - "name": "[parameters('hub').routing.networkName]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/virtualNetworks')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[parameters('hub').options.networkAddressPrefix]" - ] - }, - "subnets": "[variables('subnets')]" - }, - "dependsOn": [ - "nsg" - ] - }, - "blobPrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.dfs.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "queuePrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.queue.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "tablePrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.table.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "scriptStorageAccount": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('hub').routing.scriptStorage]", - "location": "[parameters('hub').location]", - "sku": { - "name": "Standard_LRS" - }, - "kind": "StorageV2", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/storageAccounts')]", - "properties": { - "supportsHttpsTrafficOnly": true, - "allowSharedKeyAccess": true, - "isHnsEnabled": false, - "minimumTlsVersion": "TLS1_2", - "allowBlobPublicAccess": false, - "publicNetworkAccess": "Enabled", - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Deny", - "virtualNetworkRules": [ - { - "id": "[parameters('hub').routing.subnets.scripts]", - "action": "Allow" - } - ] - } - }, - "dependsOn": [ - "vNet::scriptSubnet" - ] - }, - "scriptEndpoint": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('hub').routing.scriptStorage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "scriptLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('hub').routing.scriptStorage)]", - "groupIds": [ - "blob" - ] - } - } - ] - }, - "dependsOn": [ - "scriptStorageAccount", - "vNet::scriptSubnet" - ] + "storageSku": { + "type": "string" } - }, - "outputs": { - "config": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "FinOps hub configuration settings." - }, - "value": "[parameters('hub')]" - }, - "vNetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the virtual network." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks', parameters('hub').routing.networkName))]" - }, - "vNetAddressSpace": { - "type": "array", - "metadata": { - "description": "Virtual network address prefixes." - }, - "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').addressSpace.addressPrefixes)]" - }, - "vNetSubnets": { - "type": "array", - "metadata": { - "description": "Virtual network subnets." - }, - "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').subnets)]" - }, - "finopsHubSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the FinOps hub network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('finopsHubSubnetName')))]" - }, - "scriptSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the script storage account network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('scriptSubnetName')))]" - }, - "dataExplorerSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Explorer network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('dataExplorerSubnetName')))]" + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "Required. FinOps hub instance to deploy the app to." + } + }, + "scopesToMonitor": { + "type": "array", + "metadata": { + "description": "Optional. List of scope IDs to monitor and ingest cost for." + } + }, + "msexportRetentionInDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Number of days of data to retain in the msexports container. Default: 0." + } + }, + "ingestionRetentionInMonths": { + "type": "int", + "defaultValue": 13, + "metadata": { + "description": "Optional. Number of months of data to retain in the ingestion container. Default: 13." + } + }, + "rawRetentionInDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." + } + }, + "finalRetentionInMonths": { + "type": "int", + "defaultValue": 13, + "metadata": { + "description": "Optional. Number of months of data to retain in the Data Explorer *_final_v* tables. Default: 13." } }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + } + }, + "variables": { + "$fxv#0": "12.0", + "$fxv#1": "12.0", + "$fxv#2": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nWrite-Output \"Updating settings.json file...\"\r\nWrite-Output \" Storage account: $env:storageAccountName\"\r\nWrite-Output \" Container: $env:containerName\"\r\n\r\n$validateScopes = { $_.Length -gt 45 }\r\n\r\n# Initialize variables\r\n$fileName = 'settings.json'\r\n$filePath = Join-Path -Path . -ChildPath $fileName\r\n$newScopes = $env:scopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Download existing settings, if they exist\r\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\r\nif ($blob)\r\n{\r\n $text = Get-Content $filePath -Raw\r\n Write-Output \"---------\"\r\n Write-Output $text\r\n Write-Output \"---------\"\r\n $json = $text | ConvertFrom-Json\r\n Write-Output \"Existing settings.json file found. Updating...\"\r\n\r\n # Rename exportScopes to scopes + convert to object array\r\n if ($json.exportScopes)\r\n {\r\n Write-Output \" Updating exportScopes...\"\r\n if ($json.exportScopes[0] -is [string])\r\n {\r\n Write-Output \" Converting string array to object array...\"\r\n $json.exportScopes = @($json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } })\r\n if (-not ($json.exportScopes -is [array]))\r\n {\r\n Write-Output \" Converting single object to object array...\"\r\n $json.exportScopes = @($json.exportScopes)\r\n }\r\n }\r\n\r\n Write-Output \" Renaming to 'scopes'...\"\r\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\r\n $json.PSObject.Properties.Remove('exportScopes')\r\n }\r\n\r\n # Force string array to object array with unique values\r\n if ($json.scopes)\r\n {\r\n Write-Output \" Converting string array to object array...\"\r\n $scopeArray = @()\r\n $json.scopes | Where-Object $validateScopes | ForEach-Object { $scopeArray += $_ } | Select-Object -Unique\r\n $json.scopes = @() + $scopeArray\r\n }\r\n}\r\n\r\n# Set default if not found\r\nif (!$json)\r\n{\r\n Write-Output \"No existing settings.json file found. Creating new file...\"\r\n $json = [ordered]@{\r\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\r\n type = 'HubInstance'\r\n version = ''\r\n learnMore = 'https://aka.ms/finops/hubs'\r\n scopes = @()\r\n retention = @{\r\n 'msexports' = @{\r\n days = 0\r\n }\r\n 'ingestion' = @{\r\n months = 13\r\n }\r\n 'raw' = @{\r\n days = 0\r\n }\r\n 'final' = @{\r\n months = 13\r\n }\r\n }\r\n }\r\n\r\n $text = $json | ConvertTo-Json\r\n Write-Output \"---------\"\r\n Write-Output $text\r\n Write-Output \"---------\"\r\n}\r\n\r\n# Set default retention\r\nif (!($json.retention))\r\n{\r\n # In case the retention object is not present in the settings.json file (versions before 0.4), add it with default values\r\n $retention = @\"\r\n {\r\n \"msexports\": {\r\n \"days\": 0\r\n },\r\n \"ingestion\": {\r\n \"months\": 13\r\n },\r\n \"raw\": {\r\n \"days\": 0\r\n },\r\n \"final\": {\r\n \"months\": 13\r\n }\r\n }\r\n\"@\r\n $json | Add-Member -Name retention -Value (ConvertFrom-Json $retention) -MemberType NoteProperty\r\n}\r\n\r\n# Set or update msexports retention\r\nif (!($json.retention.msexports))\r\n{\r\n $json.retention | Add-Member -Name msexports -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:msexportRetentionInDays)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.msexports.days = [Int32]::Parse($env:msexportRetentionInDays)\r\n}\r\n\r\n# Set or update ingestion retention\r\nif (!($json.retention.ingestion))\r\n{\r\n $json.retention | Add-Member -Name ingestion -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:ingestionRetentionInMonths)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.ingestion.months = [Int32]::Parse($env:ingestionRetentionInMonths)\r\n}\r\n\r\n# Set or update raw retention\r\nif (!($json.retention.raw))\r\n{\r\n $json.retention | Add-Member -Name raw -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:rawRetentionInDays)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.raw.days = [Int32]::Parse($env:rawRetentionInDays)\r\n}\r\n\r\n# Set or update final retention\r\nif (!($json.retention.final))\r\n{\r\n $json.retention | Add-Member -Name final -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:finalRetentionInMonths)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.final.months = [Int32]::Parse($env:finalRetentionInMonths)\r\n}\r\n\r\n# Updating settings\r\nWrite-Output \"Updating version to $env:ftkVersion...\"\r\n$json.version = $env:ftkVersion\r\n$json.scopes = (@() + $json.scopes + $newScopes) | Select-Object -Unique\r\nif ($null -eq $json.scopes) { $json.scopes = @() }\r\n$text = $json | ConvertTo-Json\r\nWrite-Output \"---------\"\r\nWrite-Output $text\r\nWrite-Output \"---------\"\r\n$text | Out-File $filePath\r\n\r\n# Upload new/updated settings\r\nWrite-Output \"Uploading settings.json file...\"\r\nSet-AzStorageBlobContent @storageContext -File $filePath -Force | Out-Null\r\n" + }, + "resources": { "appRegistration": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "Microsoft.FinOpsHubs.Core_Register", "properties": { "expressionEvaluationOptions": { @@ -2213,17 +2091,32 @@ }, "mode": "Incremental", "parameters": { - "app": { - "value": "[parameters('app')]" + "hub": { + "value": "[parameters('hub')]" }, - "version": { - "value": "[variables('finOpsToolkitVersion')]" + "publisher": { + "value": "Microsoft FinOps hubs" + }, + "namespace": { + "value": "Microsoft.FinOpsHubs" + }, + "appName": { + "value": "Core" + }, + "displayName": { + "value": "FinOps hub core" + }, + "appVersion": { + "value": "[variables('$fxv#0')]" }, "features": { "value": [ "DataFactory", "Storage" ] + }, + "telemetryString": { + "value": "[parameters('telemetryString')]" } }, "template": { @@ -2233,142 +2126,53 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5436870138046688593" + "version": "0.36.177.2456", + "templateHash": "15179190433979236138" } }, "definitions": { - "_1.HubProperties": { + "_1.HubRoutingProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "name": { + "networkId": { "type": "string" }, - "location": { + "networkName": { "type": "string" }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { + "scriptStorage": { "type": "string" }, - "options": { + "dnsZones": { "type": "object", "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "publisherIsolation": { - "type": "bool" + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" }, - "storageInfrastructureEncryption": { - "type": "bool" + "queue": { + "$ref": "#/definitions/_1.IdNameObject" }, - "storageSku": { - "type": "string" + "table": { + "$ref": "#/definitions/_1.IdNameObject" } } }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { + "subnets": { "type": "object", "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { "type": "string" } } @@ -2385,7 +2189,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -2433,21 +2236,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/HubProperties" + }, "dataFactory": { "type": "string" }, @@ -2456,33 +2273,147 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" } } + }, + "HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } }, "functions": [ { "namespace": "__bicep", "members": { - "getAppPublisherTags": { + "getAppTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + }, + { + "type": "bool", + "nullable": true, + "name": "forceAppTags" + } + ], + "output": { + "type": "object", + "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "getPublisherTags": { "parameters": [ { "$ref": "#/definitions/HubAppProperties", @@ -2495,7 +2426,7 @@ ], "output": { "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" }, "metadata": { "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", @@ -2503,41 +2434,175 @@ "sourceTemplate": "hub-types.bicep" } } + }, + "newApp": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" + }, + { + "type": "string", + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "appPartialName" + }, + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" + } + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" + }, + "metadata": { + "description": "Creates a new FinOps hub app configuration object.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "version": { - "type": "string", - "metadata": { - "description": "Required. Version number of the FinOps hub app." - } }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." - } + { + "namespace": "_1", + "members": { + "newAppInternal": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherSuffix" + }, + { + "type": "object", + "name": "publisherTags" + }, + { + "type": "string", + "name": "appName" + }, + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" + } + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": { + "name": "[parameters('appName')]", + "displayName": "[parameters('appDisplayName')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", + "publisher": { + "name": "[parameters('publisherName')]", + "displayName": "[parameters('publisherDisplayName')]", + "suffix": "[parameters('publisherSuffix')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" + }, + "hub": "[parameters('hub')]", + "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "safeStorageName": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "string", + "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "Required. FinOps hub instance properties." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app publisher." + } + }, + "namespace": { + "type": "string", + "metadata": { + "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "appName": { + "type": "string", + "metadata": { + "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app." + } + }, + "appVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Version number of the FinOps hub app." + } }, - "storageRoles": { + "features": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/HubAppFeature" }, "defaultValue": [], "metadata": { - "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." } }, "telemetryString": { @@ -2549,11 +2614,11 @@ } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", "telemetryProps": { "mode": "Incremental", "template": { @@ -2561,158 +2626,30 @@ "contentVersion": "1.0.0.0", "metadata": { "_generator": { - "name": "[format('FTK: {0}', parameters('app').id)]", - "version": "[parameters('version')]" + "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", + "version": "[parameters('appVersion')]" } }, "resources": [] } }, - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", - "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" }, "resources": { - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", - "properties": { - "name": "[parameters('app').storage]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "storageAccount" - ] - }, - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", - "properties": { - "name": "[parameters('app').keyVault]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "keyVault" - ] - }, - "dataFactory::managedVirtualNetwork": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "properties": {}, - "dependsOn": [ - "dataFactory" - ] - }, - "dataFactory::managedIntegrationRuntime": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", - "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "default", - "type": "ManagedVirtualNetworkReference" - }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('app').hub.location]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 - } - } - } - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedVirtualNetwork" - ] - }, - "dataFactory::linkedService_keyVault": { - "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "keyVault" - ] - }, - "dataFactory::linkedService_storageAccount": { - "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "storageAccount" - ] - }, "storageAccount::blobService": { "condition": "[variables('usesStorage')]", "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "name": "[format('{0}/{1}', variables('app').storage, 'default')]", "dependsOn": [ "storageAccount" ] }, "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", + "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", "properties": { "privateDnsZoneConfigs": [ { @@ -2728,10 +2665,10 @@ ] }, "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", "properties": { "privateDnsZoneConfigs": [ { @@ -2750,7 +2687,7 @@ "condition": "[variables('usesKeyVault')]", "type": "Microsoft.KeyVault/vaults/accessPolicies", "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", "properties": { "accessPolicies": [ { @@ -2770,15 +2707,15 @@ ] }, "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", "apiVersion": "2024-06-01", "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" + "id": "[parameters('hub').routing.networkId]" }, "registrationEnabled": false }, @@ -2787,10 +2724,10 @@ ] }, "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", "properties": { "privateDnsZoneConfigs": [ { @@ -2807,20 +2744,20 @@ ] }, "appTelemetry": { - "condition": "[parameters('app').hub.options.enableTelemetry]", + "condition": "[parameters('hub').options.enableTelemetry]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", "properties": "[variables('telemetryProps')]" }, "dataFactory": { "condition": "[variables('usesDataFactory')]", "type": "Microsoft.DataFactory/factories", "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "name": "[variables('app').dataFactory]", + "location": "[variables('app').hub.location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", "identity": { "type": "SystemAssigned" }, @@ -2830,92 +2767,42 @@ } } }, - "storageRoleAssignments": { - "copy": { - "name": "storageRoleAssignments", - "count": "[length(variables('factoryStorageRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "storageAccount" - ] - }, - "triggerManagerIdentity": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "dependsOn": [ - "dataFactory" - ] - }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "triggerManagerIdentity" - ] - }, "storageAccount": { "condition": "[variables('usesStorage')]", "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "location": "[parameters('app').hub.location]", + "name": "[variables('app').storage]", + "location": "[parameters('hub').location]", "sku": { - "name": "[parameters('app').hub.options.storageSku]" + "name": "[parameters('hub').options.storageSku]" }, "kind": "BlockBlobStorage", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" }, "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "existing": true, "type": "Microsoft.Network/privateDnsZones", "apiVersion": "2024-06-01", "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" }, "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "name": "[format('{0}-blob-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" + "id": "[parameters('hub').routing.subnets.storage]" }, "privateLinkServiceConnections": [ { "name": "blobLink", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", "groupIds": [ "blob" ] @@ -2928,28 +2815,28 @@ ] }, "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "existing": true, "type": "Microsoft.Network/privateDnsZones", "apiVersion": "2024-06-01", "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" }, "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "name": "[format('{0}-dfs-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" + "id": "[parameters('hub').routing.subnets.storage]" }, "privateLinkServiceConnections": [ { "name": "dfsLink", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", "groupIds": [ "dfs" ] @@ -2965,12 +2852,12 @@ "condition": "[variables('usesKeyVault')]", "type": "Microsoft.KeyVault/vaults", "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "name": "[variables('app').keyVault]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", "properties": { "sku": { - "name": "[parameters('app').hub.options.keyVaultSku]", + "name": "[parameters('hub').options.keyVaultSku]", "family": "A" }, "enabledForDeployment": true, @@ -2994,7 +2881,7 @@ ], "networkAcls": { "bypass": "AzureServices", - "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" } }, "dependsOn": [ @@ -3002,30 +2889,30 @@ ] }, "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateDnsZones", "apiVersion": "2024-06-01", "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", "properties": {} }, "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('app').keyVault)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "name": "[format('{0}-ep', variables('app').keyVault)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { "subnet": { - "id": "[parameters('app').hub.routing.subnets.keyVault]" + "id": "[parameters('hub').routing.subnets.keyVault]" }, "privateLinkServiceConnections": [ { "name": "keyVaultLink", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", "groupIds": [ "vault" ] @@ -3036,305 +2923,356 @@ "dependsOn": [ "keyVault" ] + } + }, + "outputs": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "FinOps hub app configuration." + }, + "value": "[variables('app')]" }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetKeyVaultPrivateEndpointConnections", + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + } + } + } + } + }, + "configContainer": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_Storage.ConfigContainer", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('appRegistration').outputs.app.value]" + }, + "container": { + "value": "config" + }, + "forceCreateBlobManagerIdentity": { + "value": true + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "13960345490822271084" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - } + "name": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } } }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", - "getStoragePrivateEndpointConnections", - "keyVault" - ] + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", + "_1.HubRoutingProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "networkId": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } + "networkName": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" } } } }, - "dependsOn": [ - "getKeyVaultPrivateEndpointConnections", - "keyVault" - ] + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "getStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetStoragePrivateEndpointConnections", + "_1.IdNameObject": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - } + "id": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } + "name": { + "type": "string" } }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", - "stopTriggers", - "storageAccount" - ] + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "approveStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveStoragePrivateEndpointConnections", + "HubAppProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "name": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } + "displayName": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" } } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" } }, - "dependsOn": [ - "getStoragePrivateEndpointConnections", - "storageAccount" - ] + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app that storage is getting updated for." + } + }, + "container": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage container to create or update." + } + }, + "files": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." + } + }, + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" + }, + "resources": { + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "properties": { + "publicAccess": "None", + "metadata": {} + } + }, + "storageAccount::blobService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]" }, - "stopTriggers": { + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Identity', deployment().name)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -3345,28 +3283,15 @@ "value": "[parameters('app')]" }, "identityName": { - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" + "value": "[format('{0}_blobManager', parameters('app').storage)]" }, - "arguments": { - "value": "-Stop" + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, - "environmentVariables": { + "roles": { "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('app').dataFactory]" - } + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" ] } }, @@ -3377,22 +3302,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "version": "0.36.177.2456", + "templateHash": "4534337491931150093" } }, "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "_1.HubProperties": { "type": "object", "properties": { @@ -3511,9 +3425,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -3540,7 +3451,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -3574,21 +3484,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -3597,21 +3521,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -3619,500 +3544,151 @@ } } }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." + "description": "Required. FinOps hub app the identity is associated with." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Required. Name of the user assigned identity." } }, - "arguments": { + "roleAssignmentResourceId": { "type": "string", - "defaultValue": "", "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." + "description": "Required. Resource ID of the resource access is being granted for." } }, - "environmentVariables": { + "roles": { "type": "array", "items": { - "$ref": "#/definitions/EnvironmentVariable" + "type": "string" }, - "defaultValue": [], "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "description": "Required. List of RBAC role assignment GUIDs." } } }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, "resources": { "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", "location": "[parameters('app').hub.location]" }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" + "count": "[length(parameters('roles'))]" }, - "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] } - } - } - }, - "dependsOn": [ - "appTelemetry", - "dataFactory", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" - ] - } - }, - "outputs": { - "dataFactoryId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Factory instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" - }, - "keyVaultId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Key Vault instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" - }, - "storageAccountId": { - "type": "string", - "metadata": { - "description": "Resource ID of the storage account instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - }, - "triggerManagerIdentityName": { - "type": "string", - "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." - }, - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - } - } - } - }, - "dependsOn": [ - "infrastructure" - ] - }, - "configContainer": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core_Storage.ConfigContainer", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "container": { - "value": "[variables('CONFIG')]" - }, - "forceCreateBlobManagerIdentity": { - "value": true - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "7314877606184110283" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" }, - "storageInfrastructureEncryption": { - "type": "bool" + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." + }, + "value": "[parameters('identityName')]" }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" } } } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } } }, - "_1.HubRoutingProperties": { - "type": "object", + "uploadFiles": { + "condition": "[variables('hasFiles')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Upload', deployment().name)]", "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." - } - }, - "container": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage container to create or update." - } - }, - "files": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." - } - }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" - }, - "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", - "properties": { - "publicAccess": "None", - "metadata": {} - } - }, - "storageAccount::blobService": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" - }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Identity', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "roles": { - "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" - ] + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[reference('identity').outputs.name.value]" + }, + "environmentVariables": { + "value": [ + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" + }, + { + "name": "files", + "value": "[string(parameters('files'))]" + } + ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" } }, "template": { @@ -4122,11 +3698,22 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2980528181281411934" + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } }, "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "_1.HubProperties": { "type": "object", "properties": { @@ -4245,9 +3832,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -4274,7 +3858,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -4308,21 +3891,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -4331,21 +3928,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -4353,779 +3951,392 @@ } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the identity is associated with." + "description": "Required. FinOps hub app the deployment script is being run for." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the user assigned identity." + "description": "Required. Name of the managed identity to create." } }, - "roleAssignmentResourceId": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." } }, - "roles": { - "type": "array", - "items": { - "type": "string" - }, + "scriptContent": { + "type": "string", "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." + "description": "Required. Name of the deployment script to create." } - } - }, - "resources": { - "identity": { + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", "location": "[parameters('app').hub.location]" }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" + "count": "[length(variables('privateEndpointDeploymentRoles'))]" }, + "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } }, - "value": "[reference('identity').principalId]" + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } - } + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Upload', deployment().name)]", + "filesUploaded": { + "type": "int", + "metadata": { + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" + }, + "identityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + } + } + } + }, + "dependsOn": [ + "appRegistration" + ] + }, + "ingestionContainer": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_Storage.IngestionContainer", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('appRegistration').outputs.app.value]" + }, + "container": { + "value": "ingestion" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "13960345490822271084" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "queue": { + "$ref": "#/definitions/_1.IdNameObject" }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } + "table": { + "$ref": "#/definitions/_1.IdNameObject" } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "keyVault": { + "type": "string" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] + "scripts": { + "type": "string" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "storage": { + "type": "string" } } } }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "containerName": { - "type": "string", - "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" - }, - "filesUploaded": { - "type": "int", - "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" - }, - "identityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" - }, - "identityName": { - "type": "string", "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "identityPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" - } - } - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "ingestionContainer": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core_Storage.IngestionContainer", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "container": { - "value": "[variables('INGESTION')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "7314877606184110283" - } - }, - "definitions": { - "_1.HubProperties": { + "_1.IdNameObject": { "type": "object", "properties": { "id": { "type": "string" }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { "name": { "type": "string" }, - "location": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { + "publisher": { "type": "object", "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { + "name": { "type": "string" }, - "networkAddressPrefix": { + "displayName": { "type": "string" }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { + "suffix": { "type": "string" + }, + "tags": { + "type": "object" } } }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" + "hub": { + "$ref": "#/definitions/_1.HubProperties" }, "dataFactory": { "type": "string" @@ -5135,21 +4346,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -5186,7 +4398,7 @@ } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", + "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", "fileCount": "[length(items(parameters('files')))]", "hasFiles": "[greater(variables('fileCount'), 0)]" }, @@ -5215,7 +4427,7 @@ "identity": { "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}.Identity', deployment().name)]", "properties": { "expressionEvaluationOptions": { @@ -5246,8 +4458,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2980528181281411934" + "version": "0.36.177.2456", + "templateHash": "4534337491931150093" } }, "definitions": { @@ -5369,9 +4581,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -5398,7 +4607,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -5432,21 +4640,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -5455,21 +4677,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -5481,7 +4704,7 @@ { "namespace": "__bicep", "members": { - "getAppPublisherTags": { + "getPublisherTags": { "parameters": [ { "$ref": "#/definitions/HubAppProperties", @@ -5494,7 +4717,7 @@ ], "output": { "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" }, "metadata": { "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", @@ -5540,7 +4763,7 @@ "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", "location": "[parameters('app').hub.location]" }, "identityRoleAssignments": { @@ -5590,7 +4813,7 @@ "uploadFiles": { "condition": "[variables('hasFiles')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}.Upload', deployment().name)]", "properties": { "expressionEvaluationOptions": { @@ -5631,8 +4854,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } }, "definitions": { @@ -5765,9 +4988,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -5794,7 +5014,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -5828,21 +5047,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -5851,21 +5084,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -5919,8 +5153,7 @@ }, "variables": { "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" }, "resources": { "identity": { @@ -5969,7 +5202,7 @@ "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} } }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", "dependsOn": [ "identity", "identityRoleAssignments" @@ -6028,7 +5261,7 @@ }, "uploadSettings": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "Microsoft.FinOpsHubs.Core_Storage.UpdateSettings", "properties": { "expressionEvaluationOptions": { @@ -6037,22 +5270,19 @@ "mode": "Incremental", "parameters": { "app": { - "value": "[parameters('app')]" + "value": "[reference('appRegistration').outputs.app.value]" }, "identityName": { "value": "[reference('configContainer').outputs.identityName.value]" }, "scriptName": { - "value": "[format('{0}_uploadSettings', parameters('app').storage)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" + "value": "[format('{0}_uploadSettings', reference('appRegistration').outputs.app.value.storage)]" }, "environmentVariables": { "value": [ { "name": "ftkVersion", - "value": "[variables('finOpsToolkitVersion')]" + "value": "[variables('$fxv#1')]" }, { "name": "scopes", @@ -6076,13 +5306,16 @@ }, { "name": "storageAccountName", - "value": "[parameters('app').storage]" + "value": "[reference('appRegistration').outputs.app.value.storage]" }, { "name": "containerName", "value": "config" } ] + }, + "scriptContent": { + "value": "[variables('$fxv#2')]" } }, "template": { @@ -6092,8 +5325,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } }, "definitions": { @@ -6226,9 +5459,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -6255,7 +5485,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -6289,21 +5518,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -6312,21 +5555,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -6380,8 +5624,7 @@ }, "variables": { "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" }, "resources": { "identity": { @@ -6430,7 +5673,7 @@ "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} } }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", "dependsOn": [ "identity", "identityRoleAssignments" @@ -6446,55 +5689,65 @@ } }, "outputs": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Properties of the hub app." - }, - "value": "[parameters('app')]" - }, "dataFactoryName": { "type": "string", "metadata": { "description": "Name of the Data Factory." }, - "value": "[parameters('app').dataFactory]" + "value": "[reference('appRegistration').outputs.app.value.dataFactory]" }, "storageAccountName": { "type": "string", "metadata": { "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." }, - "value": "[parameters('app').storage]" + "value": "[reference('appRegistration').outputs.app.value.storage]" + }, + "configContainer": { + "type": "string", + "metadata": { + "description": "The name of the container used for configuration settings." + }, + "value": "[reference('configContainer').outputs.containerName.value]" + }, + "ingestionContainer": { + "type": "string", + "metadata": { + "description": "The name of the container used for normalized data ingestion." + }, + "value": "[reference('ingestionContainer').outputs.containerName.value]" }, "storageUrlForPowerBI": { "type": "string", "metadata": { "description": "URL to use when connecting custom Power BI reports to your data." }, - "value": "[format('https://{0}.dfs.{1}/{2}', parameters('app').storage, environment().suffixes.storage, variables('INGESTION'))]" + "value": "[format('https://{0}.dfs.{1}/{2}', reference('appRegistration').outputs.app.value.storage, environment().suffixes.storage, reference('ingestionContainer').outputs.containerName.value)]" }, "principalId": { "type": "string", "metadata": { "description": "Object ID of the Data Factory managed identity. This will be needed when configuring managed exports." }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + "value": "[reference('appRegistration').outputs.principalId.value]" }, - "triggerManagerIdentityName": { - "type": "string", + "publisherTags": { + "type": "object", "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." + "description": "Tags for the FinOps hub publisher." }, - "value": "[reference('appRegistration').outputs.triggerManagerIdentityName.value]" + "value": "[reference('appRegistration').outputs.app.value.publisher.tags]" } } } - } + }, + "dependsOn": [ + "infrastructure" + ] }, "cmExports": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "Microsoft.CostManagement.Exports", "properties": { "expressionEvaluationOptions": { @@ -6502,8 +5755,8 @@ }, "mode": "Incremental", "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft.CostManagement', 'Exports')]" + "hub": { + "value": "[variables('hub')]" } }, "template": { @@ -6513,97 +5766,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "12146592525418089958" + "version": "0.36.177.2456", + "templateHash": "12652260421176486151" } }, "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, "_1.HubRoutingProperties": { "type": "object", "properties": { @@ -6636,9 +5803,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -6665,7 +5829,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -6673,7 +5836,7 @@ }, "description": "FinOps hub private network routing properties.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -6692,11 +5855,11 @@ "name": "Resource name.", "description": "Resource ID and name.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, - "HubAppProperties": { + "HubProperties": { "type": "object", "properties": { "id": { @@ -6705,1960 +5868,2174 @@ "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "location": { "type": "string" }, "tags": { "type": "object" }, - "dataFactory": { - "type": "string" + "tagsByResource": { + "type": "object" }, - "keyVault": { + "version": { "type": "string" }, - "storage": { - "type": "string" + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } } }, "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", + "hub": { + "$ref": "#/definitions/HubProperties", "metadata": { - "description": "Required. FinOps hub app getting deployed." + "description": "Required. FinOps hub instance to deploy the app to." } } }, "variables": { - "$fxv#0": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\"}\n },\n {\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\"}\n },\n {\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerId\"}\n },\n {\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionId\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionName\"}\n },\n {\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"Date\"}\n },\n {\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\"}\n },\n {\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\"}\n },\n {\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\"}\n },\n {\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\"}\n },\n {\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\"}\n },\n {\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\"}\n },\n {\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\"}\n },\n {\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectivePrice\"}\n },\n {\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Cost\"}\n },\n {\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\"}\n },\n {\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\"}\n },\n {\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceLocation\"}\n },\n {\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\"}\n },\n {\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedService\"}\n },\n {\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\"}\n },\n {\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo1\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo2\"}\n },\n {\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AdditionalInfo\"}\n },\n {\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSection\"}\n },\n {\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\"}\n },\n {\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\"}\n },\n {\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceGroup\"}\n },\n {\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\"}\n },\n {\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationName\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderId\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderName\"}\n },\n {\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferId\"}\n },\n {\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\n },\n {\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\"}\n },\n {\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\"}\n },\n {\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PlanName\"}\n },\n {\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeType\"}\n },\n {\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Frequency\"}\n },\n {\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherType\"}\n }\n ]\n }\n}\n", - "$fxv#1": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\"}\n },\n {\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\"}\n },\n {\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerId\"}\n },\n {\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionId\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionName\"}\n },\n {\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"Date\"}\n },\n {\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\"}\n },\n {\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\"}\n },\n {\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\"}\n },\n {\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\"}\n },\n {\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\"}\n },\n {\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\"}\n },\n {\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\"}\n },\n {\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectivePrice\"}\n },\n {\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Cost\"}\n },\n {\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\"}\n },\n {\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\"}\n },\n {\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceLocation\"}\n },\n {\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\"}\n },\n {\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedService\"}\n },\n {\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\"}\n },\n {\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo1\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo2\"}\n },\n {\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AdditionalInfo\"}\n },\n {\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSection\"}\n },\n {\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\"}\n },\n {\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\"}\n },\n {\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceGroup\"}\n },\n {\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\"}\n },\n {\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationName\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderId\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderName\"}\n },\n {\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferId\"}\n },\n {\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\n },\n {\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\"}\n },\n {\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\"}\n },\n {\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PlanName\"}\n },\n {\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeType\"}\n },\n {\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Frequency\"}\n },\n {\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherType\"}\n }\n ]\n }\n}\n", - "$fxv#10": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"SKU\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SKU\" }\n },\n {\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Location\" }\n },\n {\n \"source\": { \"name\": \"CostWithNoReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostWithNoReservedInstances\" }\n },\n {\n \"source\": { \"name\": \"FirstUsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"FirstUsageDate\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\n \"sink\": { \"name\": \"LookBackPeriod\" }\n },\n {\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"NetSavings\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"NetSavings\" }\n },\n {\n \"source\": { \"name\": \"NormalizedSize\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NormalizedSize\" }\n },\n {\n \"source\": { \"name\": \"RecommendedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantity\" }\n },\n {\n \"source\": { \"name\": \"RecommendedQuantityNormalized\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"Scope\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Scope\" }\n },\n {\n \"source\": { \"name\": \"SkuProperties\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuProperties\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"TotalCostWithReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TotalCostWithReservedInstances\" }\n }\n ]\n }\n}\n", - "$fxv#11": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"Cost With No ReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostWithNoReservedInstancesJson\" }\n },\n {\n \"source\": { \"name\": \"First UsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"FirstUsageDate\" }\n },\n {\n \"source\": { \"name\": \"Instance Flexibility Ratio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"Instance Flexibility Group\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Location\" }\n },\n {\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\n \"sink\": { \"name\": \"LookBackPeriod\" }\n },\n {\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"Net Savings\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NetSavingsJson\" }\n },\n {\n \"source\": { \"name\": \"Normalized Size\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NormalizedSize\" }\n },\n {\n \"source\": { \"name\": \"Recommended Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantity\" }\n },\n {\n \"source\": { \"name\": \"Recommended Quantity Normalized\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"scope\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Scope\" }\n },\n {\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuName\" }\n },\n {\n \"source\": { \"name\": \"Sku Properties\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuProperties\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"Total Cost With ReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TotalCostWithReservedInstancesJson\" }\n }\n ]\n }\n}\n", - "$fxv#12": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\" }\n },\n {\n \"source\": { \"name\": \"AccountOwnerEmail\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerEmail\" }\n },\n {\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Amount\" }\n },\n {\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ArmSkuName\" }\n },\n {\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingFrequency\" }\n },\n {\n \"source\": { \"name\": \"BillingMonth\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingMonth\" }\n },\n {\n \"source\": { \"name\": \"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"CurrentEnrollmentId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CurrentEnrollmentId\" }\n },\n {\n \"source\": { \"name\": \"DepartmentName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"DepartmentName\" }\n },\n {\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Description\" }\n },\n {\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EventDate\" }\n },\n {\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EventType\" }\n },\n {\n \"source\": { \"name\": \"MonetaryCommitment\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MonetaryCommitment\" }\n },\n {\n \"source\": { \"name\": \"Overage\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Overage\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\n },\n {\n \"source\": { \"name\": \"PurchasingEnrollment\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingEnrollment\" }\n },\n {\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderName\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n }\n ]\n }\n}\n", - "$fxv#13": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Amount\" }\n },\n {\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ArmSkuName\" }\n },\n {\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingFrequency\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Description\" }\n },\n {\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EventDate\" }\n },\n {\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EventType\" }\n },\n {\n \"source\": { \"name\": \"Invoice\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Invoice\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\n },\n {\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderName\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n }\n ]\n }\n}\n", - "$fxv#2": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationId\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceSubcategory\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuMeter\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceDetails\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AmortizationClass\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ServiceModel\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPlanName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", - "$fxv#3": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationId\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceSubcategory\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuMeter\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceDetails\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AmortizationClass\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ServiceModel\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPlanName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", - "$fxv#4": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", - "$fxv#5": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", - "$fxv#6": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\" }\n },\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeSubcategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"UsageQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UsageQuantity\" }\n },\n {\n \"source\": { \"name\": \"UsageUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UsageUnit\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ChargeId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ChargeId\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandCost\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", - "$fxv#7": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"EnrollmentNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EnrollmentNumber\" }\n },\n {\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterID\" }\n },\n {\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\" }\n },\n {\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterType\" }\n },\n {\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\" }\n },\n {\n \"source\": { \"name\": \"SkuID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuID\" }\n },\n {\n \"source\": { \"name\": \"ProductID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductID\" }\n },\n {\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\" }\n },\n {\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\" }\n },\n {\n \"source\": { \"name\": \"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\" }\n },\n {\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveStartDate\" }\n },\n {\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveEndDate\" }\n },\n {\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\" }\n },\n {\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BasePrice\" }\n },\n {\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MarketPrice\" }\n },\n {\n \"source\": { \"name\": \"CurrencyCode\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CurrencyCode\" }\n },\n {\n \"source\": { \"name\": \"IncludedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"IncludedQuantity\" }\n },\n {\n \"source\": { \"name\": \"OfferID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferID\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PriceType\" }\n }\n ]\n }\n}\n", - "$fxv#8": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\" }\n },\n {\n \"source\": { \"name\": \"ProductId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductId\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\" }\n },\n {\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\" }\n },\n {\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterType\" }\n },\n {\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\" }\n },\n {\n \"source\": { \"name\": \"TierMinimumUnits\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TierMinimumUnits\" }\n },\n {\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveStartDate\" }\n },\n {\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveEndDate\" }\n },\n {\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\" }\n },\n {\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BasePrice\" }\n },\n {\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MarketPrice\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PriceType\" }\n }\n ]\n }\n}\n", - "$fxv#9": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"InstanceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceId\" }\n },\n {\n \"source\": { \"name\": \"Kind\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Kind\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\" }\n },\n {\n \"source\": { \"name\": \"ReservedHours\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ReservedHours\" }\n },\n {\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuName\" }\n },\n {\n \"source\": { \"name\": \"TotalReservedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"TotalReservedQuantity\" }\n },\n {\n \"source\": { \"name\": \"UsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"UsageDate\" }\n },\n {\n \"source\": { \"name\": \"UsedHours\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UsedHours\" }\n }\n ]\n }\n}\n", - "CONFIG": "config", - "INGESTION": "ingestion", - "MSEXPORTS": "msexports", - "ingestionIdFileNameSeparator": "__", - "finOpsToolkitVersion": "12.0" + "$fxv#0": "12.0", + "$fxv#1": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"Date\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectivePrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Cost\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceLocation\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedService\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo1\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo2\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AdditionalInfo\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSection\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceGroup\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PlanName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeType\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Frequency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherType\"}\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#10": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Kind\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Kind\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservedHours\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ReservedHours\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TotalReservedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"TotalReservedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"UsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsedHours\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UsedHours\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#11": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"SKU\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SKU\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Location\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CostWithNoReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostWithNoReservedInstances\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"FirstUsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"FirstUsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"LookBackPeriod\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"NetSavings\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"NetSavings\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"NormalizedSize\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NormalizedSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RecommendedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RecommendedQuantityNormalized\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Scope\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Scope\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuProperties\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuProperties\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TotalCostWithReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TotalCostWithReservedInstances\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#12": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"Cost With No ReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostWithNoReservedInstancesJson\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"First UsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"FirstUsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Instance Flexibility Ratio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Instance Flexibility Group\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Location\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"LookBackPeriod\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Net Savings\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NetSavingsJson\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Normalized Size\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NormalizedSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Recommended Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Recommended Quantity Normalized\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"scope\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Scope\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Sku Properties\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuProperties\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Total Cost With ReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TotalCostWithReservedInstancesJson\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#13": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"AccountOwnerEmail\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerEmail\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Amount\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ArmSkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingMonth\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingMonth\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CurrentEnrollmentId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CurrentEnrollmentId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"DepartmentName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"DepartmentName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Description\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EventDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EventType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MonetaryCommitment\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MonetaryCommitment\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Overage\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Overage\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingEnrollment\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingEnrollment\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#14": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Amount\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ArmSkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Description\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EventDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EventType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Invoice\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Invoice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#2": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"Date\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectivePrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Cost\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceLocation\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedService\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo1\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo2\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AdditionalInfo\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSection\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceGroup\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PlanName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeType\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Frequency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherType\"}\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#3": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuMeter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AmortizationClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ServiceModel\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPlanName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#4": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuMeter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AmortizationClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ServiceModel\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPlanName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#5": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#6": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#7": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UsageQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UsageUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ChargeId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ChargeId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#8": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"EnrollmentNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EnrollmentNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProductID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveStartDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveEndDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BasePrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MarketPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CurrencyCode\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CurrencyCode\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"IncludedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"IncludedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"OfferID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PriceType\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#9": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProductId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TierMinimumUnits\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TierMinimumUnits\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveStartDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveEndDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BasePrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MarketPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PriceType\" }\r\n }\r\n ]\r\n }\r\n}\r\n" }, "resources": { - "dataFactory::linkedService_storageAccount": { - "existing": true, - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_config": { - "existing": true, - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]", - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_ingestion": { - "existing": true, - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('INGESTION'))]", - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_ingestion_files": { - "existing": true, - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', variables('INGESTION')))]", - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_manifest": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'manifest')]", + "appRegistration": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.CostManagement.Exports_Register", "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", "parameters": { - "fileName": { - "type": "String", - "defaultValue": "manifest.json" + "hub": { + "value": "[parameters('hub')]" }, - "folderPath": { - "type": "String", - "defaultValue": "[variables('MSEXPORTS')]" + "publisher": { + "value": "Microsoft FinOps hubs" + }, + "namespace": { + "value": "Microsoft.FinOpsHubs" + }, + "appName": { + "value": "Core" + }, + "displayName": { + "value": "FinOps hub core" + }, + "appVersion": { + "value": "[variables('$fxv#0')]" + }, + "features": { + "value": [ + "DataFactory", + "Storage" + ] } }, - "type": "Json", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().fileName}", - "type": "Expression" - }, - "folderPath": { - "value": "@{dataset().folderPath}", - "type": "Expression" - } - } - }, - "linkedServiceName": { - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_msexports": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, replace(format('{0}', variables('MSEXPORTS')), '-', '_'))]", - "properties": { - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" - }, - "fileSystem": "[reference('exportContainer').outputs.containerName.value]" - }, - "columnDelimiter": ",", - "escapeChar": "\"", - "quoteChar": "\"", - "firstRowAsHeader": true - }, - "linkedServiceName": { - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "appRegistration", - "exportContainer" - ] - }, - "dataFactory::dataset_msexports_gzip": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_gzip', variables('MSEXPORTS')))]", - "properties": { - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" - }, - "fileSystem": "[variables('MSEXPORTS')]" - }, - "columnDelimiter": ",", - "escapeChar": "\"", - "quoteChar": "\"", - "firstRowAsHeader": true, - "compressionCodec": "Gzip" - }, - "linkedServiceName": { - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_msexports_parquet": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_parquet', variables('MSEXPORTS')))]", - "properties": { - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" - }, - "fileSystem": "[variables('MSEXPORTS')]" - } - }, - "linkedServiceName": { - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::pipeline_ExecuteExportsETL": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ExecuteETL', variables('MSEXPORTS')))]", - "properties": { - "activities": [ - { - "name": "Wait", - "description": "Files may not be available immediately after being created.", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 60 + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "15179190433979236138" } }, - { - "name": "Read Manifest", - "description": "Load the export manifest to determine the scope, dataset, and date range.", - "type": "Lookup", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Completed" - ] + "definitions": { + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "formatSettings": { - "type": "JsonReadSettings" + "name": { + "type": "string" } }, - "dataset": { - "referenceName": "manifest", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@pipeline().parameters.fileName", - "type": "Expression" - }, - "folderPath": { - "value": "@pipeline().parameters.folderPath", - "type": "Expression" - } + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } - } - }, - { - "name": "Set Has No Rows", - "description": "Check the row count ", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "hasNoRows", - "value": { - "value": "@or(equals(activity('Read Manifest').output.firstRow.blobCount, null), equals(activity('Read Manifest').output.firstRow.blobCount, 0))", - "type": "Expression" - } - } - }, - { - "name": "Set Export Dataset Type", - "description": "Save the dataset type from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "exportDatasetType", - "value": { - "value": "@activity('Read Manifest').output.firstRow.exportConfig.type", - "type": "Expression" - } - } - }, - { - "name": "Set MCA Column", - "description": "Determines if the dataset schema has channel-specific columns and saves the column name that only exists in MCA to determine if it is an MCA dataset.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Succeeded" - ] + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "mcaColumnToCheck", - "value": { - "value": "@if(contains(createArray('pricesheet', 'reservationtransactions'), toLower(variables('exportDatasetType'))), 'BillingProfileId', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Net Savings', null))", - "type": "Expression" - } - } - }, - { - "name": "Set Export Dataset Version", - "description": "Save the dataset version from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "exportDatasetVersion", - "value": { - "value": "@activity('Read Manifest').output.firstRow.exportConfig.dataVersion", - "type": "Expression" + "HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } } }, - { - "name": "Detect Channel", - "description": "Determines what channel this export is from. Switch statement handles the different file types if the mcaColumnToCheck variable is set.", - "type": "Switch", - "dependsOn": [ - { - "activity": "Set Has No Rows", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set MCA Column", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Export Dataset Version", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "on": { - "value": "@if(or(empty(variables('mcaColumnToCheck')), variables('hasNoRows')), 'ignore', last(array(split(activity('Read Manifest').output.firstRow.blobs[0].blobName, '.'))))", - "type": "Expression" - }, - "cases": [ - { - "value": "csv", - "activities": [ + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppTags": { + "parameters": [ { - "name": "Check for MCA Column in CSV", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" - } - }, - "dataset": { - "referenceName": "[replace(format('{0}', variables('MSEXPORTS')), '-', '_')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" - } - } - } - } + "$ref": "#/definitions/HubAppProperties", + "name": "app" }, { - "name": "Set Schema File with Channel in CSV", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in CSV", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in CSV').output, 'firstRow'), contains(activity('Check for MCA Column in CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" - } - } + "type": "string", + "name": "resourceType" + }, + { + "type": "bool", + "nullable": true, + "name": "forceAppTags" } - ] + ], + "output": { + "type": "object", + "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - { - "value": "gz", - "activities": [ + "getPublisherTags": { + "parameters": [ { - "name": "Check for MCA Column in Gzip CSV", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" - } - }, - "dataset": { - "referenceName": "[format('{0}_gzip', variables('MSEXPORTS'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" - } - } - } - } + "$ref": "#/definitions/HubAppProperties", + "name": "app" }, { - "name": "Set Schema File with Channel in Gzip CSV", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in Gzip CSV", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Gzip CSV').output, 'firstRow'), contains(activity('Check for MCA Column in Gzip CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" - } - } + "type": "string", + "name": "resourceType" } - ] + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - { - "value": "parquet", - "activities": [ + "newApp": { + "parameters": [ { - "name": "Check for MCA Column in Parquet", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "ParquetSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" - } - }, - "dataset": { - "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" - } - } - } - } + "$ref": "#/definitions/HubProperties", + "name": "hub" }, { - "name": "Set Schema File with Channel for Parquet", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in Parquet", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Parquet').output, 'firstRow'), contains(activity('Check for MCA Column in Parquet').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" - } - } + "type": "string", + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "appPartialName" + }, + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" } - ] - } - ], - "defaultActivities": [ - { - "name": "Set Schema File", - "type": "SetVariable", - "dependsOn": [], - "policy": { - "secureOutput": false, - "secureInput": false + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), '.json'))", - "type": "Expression" + "metadata": { + "description": "Creates a new FinOps hub app configuration object.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } } - ] - } - }, - { - "name": "Set Scope", - "description": "Save the scope from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "scope", - "value": { - "value": "@split(toLower(activity('Read Manifest').output.firstRow.exportConfig.resourceId), '/providers/microsoft.costmanagement/exports/')[0]", - "type": "Expression" + { + "namespace": "_1", + "members": { + "newAppInternal": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherSuffix" + }, + { + "type": "object", + "name": "publisherTags" + }, + { + "type": "string", + "name": "appName" + }, + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" + } + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": { + "name": "[parameters('appName')]", + "displayName": "[parameters('appDisplayName')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", + "publisher": { + "name": "[parameters('publisherName')]", + "displayName": "[parameters('publisherDisplayName')]", + "suffix": "[parameters('publisherSuffix')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" + }, + "hub": "[parameters('hub')]", + "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "safeStorageName": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "string", + "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } } } - }, - { - "name": "Set Date", - "description": "Save the exported month from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] + ], + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "Required. FinOps hub instance properties." } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "date", - "value": { - "value": "@replace(substring(activity('Read Manifest').output.firstRow.runInfo.startDate, 0, 7), '-', '')", - "type": "Expression" + "publisher": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app publisher." + } + }, + "namespace": { + "type": "string", + "metadata": { + "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "appName": { + "type": "string", + "metadata": { + "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app." + } + }, + "appVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Version number of the FinOps hub app." + } + }, + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." + } + }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." } } }, - { - "name": "Failed to Read Manifest", - "type": "Fail", - "dependsOn": [ - { - "activity": "Set Date", - "dependencyConditions": [ - "Failed" + "variables": { + "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", + "version": "[parameters('appVersion')]" + } + }, + "resources": [] + } + }, + "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" + }, + "resources": { + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', variables('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } ] }, - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Failed" + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } ] }, - { - "activity": "Set Scope", - "dependencyConditions": [ - "Failed" + "dependsOn": [ + "dfsEndpoint" + ] + }, + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } ] }, - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Failed" - ] + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + }, + "registrationEnabled": false }, - { - "activity": "Set Export Dataset Version", - "dependencyConditions": [ - "Failed" + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } ] }, - { - "activity": "Detect Channel", - "dependencyConditions": [ - "Failed" - ] + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('hub').options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[variables('app').dataFactory]", + "location": "[variables('app').hub.location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Failed to read the manifest file for this export run. Manifest path: ', pipeline().parameters.folderPath)", - "type": "Expression" + }, + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[variables('app').storage]", + "location": "[parameters('hub').location]", + "sku": { + "name": "[parameters('hub').options.storageSku]" }, - "errorCode": "ManifestReadFailed" - } - }, - { - "name": "Check Schema", - "description": "Verify that the schema file exists in storage.", - "type": "GetMetadata", - "dependsOn": [ - { - "activity": "Set Scope", - "dependencyConditions": [ - "Succeeded" + "kind": "BlockBlobStorage", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" + }, + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + }, + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('hub').routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", + "groupIds": [ + "blob" + ] + } + } ] }, - { - "activity": "Set Date", - "dependencyConditions": [ - "Succeeded" + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('hub').routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } ] }, - { - "activity": "Detect Channel", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "dependsOn": [ + "storageAccount" + ] }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('schemaFile')", - "type": "Expression" - }, - "folderPath": "[format('{0}/schemas', reference('schemaFiles').outputs.containerName.value)]" + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[variables('app').keyVault]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('hub').options.keyVaultSku]", + "family": "A" + }, + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" } }, - "fieldList": [ - "exists" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - } - }, - { - "name": "Schema Not Found", - "type": "Fail", - "dependsOn": [ - { - "activity": "Check Schema", - "dependencyConditions": [ - "Failed" + "dependsOn": [ + "dataFactory" + ] + }, + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', variables('app').keyVault)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('hub').routing.subnets.keyVault]" + }, + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('The ', variables('schemaFile'), ' schema mapping file was not found. Please confirm version ', variables('exportDatasetVersion'), ' of the ', variables('exportDatasetType'), ' dataset is supported by this version of FinOps hubs. You may need to upgrade to a newer release. To add support for another dataset, you can create a custom mapping file.')", - "type": "Expression" }, - "errorCode": "SchemaNotFound" + "dependsOn": [ + "keyVault" + ] } }, - { - "name": "Set Hub Dataset", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false + "outputs": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "FinOps hub app configuration." + }, + "value": "[variables('app')]" }, - "userProperties": [], - "typeProperties": { - "variableName": "hubDataset", - "value": { - "value": "@if(equals(toLower(variables('exportDatasetType')), 'focuscost'), 'Costs', if(equals(toLower(variables('exportDatasetType')), 'pricesheet'), 'Prices', if(equals(toLower(variables('exportDatasetType')), 'reservationdetails'), 'CommitmentDiscountUsage', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Recommendations', if(equals(toLower(variables('exportDatasetType')), 'reservationtransactions'), 'Transactions', if(equals(toLower(variables('exportDatasetType')), 'actualcost'), 'ActualCosts', if(equals(toLower(variables('exportDatasetType')), 'amortizedcost'), 'AmortizedCosts', toLower(variables('exportDatasetType')))))))))", - "type": "Expression" - } + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" } - }, - { - "name": "Set Destination Folder", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check Schema", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Hub Dataset", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "destinationFolder", - "value": { - "value": "@replace(concat(variables('hubDataset'),'/',substring(variables('date'), 0, 4),'/',substring(variables('date'), 4, 2),'/',toLower(variables('scope')), if(equals(variables('hubDataset'), 'Recommendations'), activity('Read Manifest').output.firstRow.exportConfig.exportName, '')),'//','/')", - "type": "Expression" - } + } + } + } + }, + "schemaFiles": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.CostManagement.Exports_Storage.SchemaFiles", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('appRegistration').outputs.app.value]" + }, + "container": { + "value": "config" + }, + "files": { + "value": { + "schemas/actualcost_c360-2025-04.json": "[variables('$fxv#1')]", + "schemas/amortizedcost_c360-2025-04.json": "[variables('$fxv#2')]", + "schemas/focuscost_1.2.json": "[variables('$fxv#3')]", + "schemas/focuscost_1.2-preview.json": "[variables('$fxv#4')]", + "schemas/focuscost_1.0r2.json": "[variables('$fxv#5')]", + "schemas/focuscost_1.0.json": "[variables('$fxv#6')]", + "schemas/focuscost_1.0-preview(v1).json": "[variables('$fxv#7')]", + "schemas/pricesheet_2023-05-01_ea.json": "[variables('$fxv#8')]", + "schemas/pricesheet_2023-05-01_mca.json": "[variables('$fxv#9')]", + "schemas/reservationdetails_2023-03-01.json": "[variables('$fxv#10')]", + "schemas/reservationrecommendations_2023-05-01_ea.json": "[variables('$fxv#11')]", + "schemas/reservationrecommendations_2023-05-01_mca.json": "[variables('$fxv#12')]", + "schemas/reservationtransactions_2023-05-01_ea.json": "[variables('$fxv#13')]", + "schemas/reservationtransactions_2023-05-01_mca.json": "[variables('$fxv#14')]" + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "13960345490822271084" } }, - { - "name": "For Each Blob", - "description": "Loop thru each exported file listed in the manifest.", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Set Destination Folder", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(variables('hasNoRows'), json('[]'), activity('Read Manifest').output.firstRow.blobs)", - "type": "Expression" - }, - "batchCount": "[if(parameters('app').hub.options.privateRouting, 4, 30)]", - "isSequential": false, - "activities": [ - { - "name": "Execute", - "description": "Run the ingestion ETL pipeline.", - "type": "ExecutePipeline", - "dependsOn": [], - "policy": { - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_ETL_{1}', variables('MSEXPORTS'), variables('INGESTION'))]", - "type": "PipelineReference" + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "waitOnCompletion": true, - "parameters": { - "blobPath": { - "value": "@item().blobName", - "type": "Expression" - }, - "destinationFolder": { - "value": "@variables('destinationFolder')", - "type": "Expression" - }, - "destinationFile": { - "value": "@last(array(split(replace(replace(item().blobName, '.gz', ''), '.csv', '.parquet'), '/')))", - "type": "Expression" - }, - "ingestionId": { - "value": "@activity('Read Manifest').output.firstRow.runInfo.runId", - "type": "Expression" - }, - "schemaFile": { - "value": "@variables('schemaFile')", - "type": "Expression" - }, - "exportDatasetType": { - "value": "@variables('exportDatasetType')", - "type": "Expression" - }, - "exportDatasetVersion": { - "value": "@variables('exportDatasetVersion')", - "type": "Expression" - } + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } - } - ] - } - }, - { - "name": "Copy Manifest", - "description": "Copy the manifest to the ingestion container to trigger ADX ingestion", - "type": "Copy", - "dependsOn": [ - { - "activity": "For Each Blob", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "sink": { - "type": "JsonSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" }, - "formatSettings": { - "type": "JsonWriteSettings" + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } } }, - "enableStaging": false - }, - "inputs": [ - { - "referenceName": "manifest", - "type": "DatasetReference", - "parameters": { - "fileName": "manifest.json", - "folderPath": { - "value": "@pipeline().parameters.folderPath", - "type": "Expression" - } + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } - ], - "outputs": [ - { - "referenceName": "manifest", - "type": "DatasetReference", - "parameters": { - "fileName": "manifest.json", - "folderPath": { - "value": "[format('@concat(''{0}/'', variables(''destinationFolder''))', variables('INGESTION'))]", - "type": "Expression" - } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } - ] - } - ], - "parameters": { - "folderPath": { - "type": "string" - }, - "fileName": { - "type": "string" - } - }, - "variables": { - "date": { - "type": "String" - }, - "destinationFolder": { - "type": "String" - }, - "exportDatasetType": { - "type": "String" - }, - "exportDatasetVersion": { - "type": "String" - }, - "hasNoRows": { - "type": "Boolean" - }, - "hubDataset": { - "type": "String" - }, - "mcaColumnToCheck": { - "type": "String" - }, - "schemaFile": { - "type": "String" - }, - "scope": { - "type": "String" - } - }, - "annotations": [ - "New export" - ] - }, - "dependsOn": [ - "appRegistration", - "dataFactory::dataset_manifest", - "dataFactory::dataset_msexports", - "dataFactory::dataset_msexports_gzip", - "dataFactory::dataset_msexports_parquet", - "dataFactory::pipeline_ToIngestion", - "schemaFiles" - ] - }, - "dataFactory::pipeline_ToIngestion": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ETL_{1}', variables('MSEXPORTS'), variables('INGESTION')))]", - "properties": { - "activities": [ - { - "name": "Get Existing Parquet Files", - "description": "Get the previously ingested files so we can remove any older data. This is necessary to avoid data duplication in reports.", - "type": "GetMetadata", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[format('{0}_files', variables('INGESTION'))]", - "type": "DatasetReference", - "parameters": { - "folderPath": "@pipeline().parameters.destinationFolder" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" } }, - "fieldList": [ - "childItems" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "enablePartitionDiscovery": false + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } }, - "formatSettings": { - "type": "ParquetReadSettings" + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } } }, - { - "name": "Filter Out Current Exports", - "description": "Remove existing files from the current export so those files do not get deleted.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Get Existing Parquet Files", - "dependencyConditions": [ - "Completed" - ] + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app that storage is getting updated for." } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", - "type": "Expression" - }, - "condition": { - "value": "[format('@and(endswith(item().name, ''.parquet''), not(startswith(item().name, concat(pipeline().parameters.ingestionId, ''{0}''))))', variables('ingestionIdFileNameSeparator'))]", - "type": "Expression" + }, + "container": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage container to create or update." + } + }, + "files": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." + } + }, + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." } } }, - { - "name": "Load Schema Mappings", - "description": "Get schema mapping file to use for the CSV to parquet conversion.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" + }, + "resources": { + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "properties": { + "publicAccess": "None", + "metadata": {} + } }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false + "storageAccount::blobService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]" + }, + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Identity', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", + "mode": "Incremental", "parameters": { - "fileName": { - "value": "@toLower(pipeline().parameters.schemaFile)", - "type": "Expression" + "app": { + "value": "[parameters('app')]" }, - "folderPath": "[format('{0}/schemas', variables('CONFIG'))]" - } - } - } - }, - { - "name": "Failed to Load Schema", - "type": "Fail", - "dependsOn": [ - { - "activity": "Load Schema Mappings", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to load the ', pipeline().parameters.schemaFile, ' schema file. Please confirm the schema and version are supported for FinOps hubs ingestion. Unsupported files will remain in the msexports container.')", - "type": "Expression" - }, - "errorCode": "SchemaLoadFailed" - } - }, - { - "name": "Set Additional Columns", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Load Schema Mappings", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "additionalColumns", - "value": { - "value": "@intersection(array(json(concat('[{\"name\":\"x_SourceProvider\",\"value\":\"Microsoft\"},{\"name\":\"x_SourceName\",\"value\":\"Cost Management\"},{\"name\":\"x_SourceType\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"},{\"name\":\"x_SourceVersion\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"}'))), activity('Load Schema Mappings').output.firstRow.additionalColumns)", - "type": "Expression" - } - } - }, - { - "name": "For Each Old File", - "description": "Loop thru each of the existing files from previous exports.", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Convert to Parquet", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Filter Out Current Exports", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@activity('Filter Out Current Exports').output.Value", - "type": "Expression" - }, - "activities": [ - { - "name": "Delete Old Ingested File", - "description": "Delete the previously ingested files from older exports.", - "type": "Delete", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "identityName": { + "value": "[format('{0}_blobManager', parameters('app').storage)]" }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[variables('INGESTION')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@concat(pipeline().parameters.destinationFolder, '/', item().name)", - "type": "Expression" - } - } - }, - "enableLogging": false, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - } + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "roles": { + "value": [ + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" + ] } - } - ] - } - }, - { - "name": "Set Destination Path", - "type": "SetVariable", - "dependsOn": [], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "destinationPath", - "value": { - "value": "[format('@concat(pipeline().parameters.destinationFolder, ''/'', pipeline().parameters.ingestionId, ''{0}'', pipeline().parameters.destinationFile)', variables('ingestionIdFileNameSeparator'))]", - "type": "Expression" - } - } - }, - { - "name": "Convert to Parquet", - "description": "[format('Convert CSV to parquet and move the file to the {0} container.', variables('INGESTION'))]", - "type": "Switch", - "dependsOn": [ - { - "activity": "Set Destination Path", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Load Schema Mappings", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Additional Columns", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "on": { - "value": "@last(array(split(pipeline().parameters.blobPath, '.')))", - "type": "Expression" - }, - "cases": [ - { - "value": "csv", - "activities": [ - { - "name": "Convert CSV File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:10:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" - } - }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" - } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "4534337491931150093" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false, - "translator": { - "value": "@activity('Load Schema Mappings').output.firstRow.translator", - "type": "Expression" - } - }, - "inputs": [ - { - "referenceName": "[replace(format('{0}', variables('MSEXPORTS')), '-', '_')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } - } - ], - "outputs": [ - { - "referenceName": "[variables('INGESTION')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } } - ] - } - ] - }, - { - "value": "gz", - "activities": [ - { - "name": "Convert GZip CSV File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:10:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" - } + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" - } + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false, - "translator": { - "value": "@activity('Load Schema Mappings').output.firstRow.translator", - "type": "Expression" + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } - }, - "inputs": [ - { - "referenceName": "[format('{0}_gzip', variables('MSEXPORTS'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } } - } - ], - "outputs": [ - { - "referenceName": "[variables('INGESTION')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" } } } - ] - } - ] - }, - { - "value": "parquet", - "activities": [ - { - "name": "Move Parquet File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "ParquetSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" - } + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" - } + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } }, - "inputs": [ - { - "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" } } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" } - ], - "outputs": [ - { - "referenceName": "[variables('INGESTION')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the identity is associated with." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the user assigned identity." + } + }, + "roleAssignmentResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource access is being granted for." + } + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of RBAC role assignment GUIDs." + } + } + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "location": "[parameters('app').hub.location]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" ] } - ] - } - ], - "defaultActivities": [ - { - "name": "Unsupported File Type", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to ingest the specified export file because the file type is not supported. File: ', pipeline().parameters.blobPath)", - "type": "Expression" + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" }, - "errorCode": "UnsupportedExportFileType" + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." + }, + "value": "[parameters('identityName')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" + } } } - ] - } - }, - { - "name": "Read Hub Config", - "description": "Read the hub config to determine if the export should be retained.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + } }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false + "uploadFiles": { + "condition": "[variables('hasFiles')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Upload', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", + "mode": "Incremental", "parameters": { - "fileName": "settings.json", - "folderPath": "[variables('CONFIG')]" - } - } - } - }, - { - "name": "If Not Retaining Exports", - "description": "If the msexports retention period <= 0, delete the source file. The main reason to keep the source file is to allow for troubleshooting and reprocessing in the future.", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Convert to Parquet", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Read Hub Config", - "dependencyConditions": [ - "Completed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@lessOrEquals(coalesce(activity('Read Hub Config').output.firstRow.retention.msexports.days, 0), 0)", - "type": "Expression" - }, - "ifTrueActivities": [ - { - "name": "Delete Source File", - "description": "Delete the exported data file to keep storage costs down. This file is not referenced by any reporting systems.", - "type": "Delete", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "app": { + "value": "[parameters('app')]" }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" - } + "identityName": { + "value": "[reference('identity').outputs.name.value]" + }, + "environmentVariables": { + "value": [ + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" + }, + { + "name": "files", + "value": "[string(parameters('files'))]" } - }, - "enableLogging": false, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - } - } - } - ] - } - } - ], - "parameters": { - "blobPath": { - "type": "String" - }, - "destinationFile": { - "type": "string" - }, - "destinationFolder": { - "type": "string" - }, - "ingestionId": { - "type": "string" - }, - "schemaFile": { - "type": "string" - }, - "exportDatasetType": { - "type": "string" - }, - "exportDatasetVersion": { - "type": "string" - } - }, - "variables": { - "additionalColumns": { - "type": "Array" - }, - "destinationPath": { - "type": "String" - } - } - }, - "dependsOn": [ - "appRegistration", - "dataFactory::dataset_msexports", - "dataFactory::dataset_msexports_gzip", - "dataFactory::dataset_msexports_parquet" - ] - }, - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "dependsOn": [ - "appRegistration" - ] - }, - "appRegistration": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.Exports_Register", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "version": { - "value": "[variables('finOpsToolkitVersion')]" - }, - "features": { - "value": [ - "Storage", - "DataFactory" - ] - }, - "storageRoles": { - "value": [ - "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5436870138046688593" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } + ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" } }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } }, - "dataFactory": { - "type": "string" + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "keyVault": { - "type": "string" + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "scripts": { - "type": "string" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } } }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" + }, + "filesUploaded": { + "type": "int", "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" }, - "HubAppFeature": { + "identityId": { "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" }, - "HubAppProperties": { + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + } + } + } + }, + "dependsOn": [ + "appRegistration" + ] + }, + "exportContainer": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.CostManagement.Exports_Storage.ExportContainer", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('appRegistration').outputs.app.value]" + }, + "container": { + "value": "msexports" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "13960345490822271084" + } + }, + "definitions": { + "_1.HubProperties": { "type": "object", "properties": { "id": { @@ -8667,38 +8044,227 @@ "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "location": { "type": "string" }, "tags": { "type": "object" }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" + "tagsByResource": { + "type": "object" }, - "storage": { + "version": { "type": "string" }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, "hub": { "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -8706,1009 +8272,188 @@ } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app getting deployed." + "description": "Required. FinOps hub app that storage is getting updated for." } }, - "version": { + "container": { "type": "string", "metadata": { - "description": "Required. Version number of the FinOps hub app." - } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." + "description": "Required. Name of the storage container to create or update." } }, - "storageRoles": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], + "files": { + "type": "object", + "defaultValue": {}, "metadata": { - "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." } }, - "telemetryString": { - "type": "string", - "defaultValue": "", + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." } } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0}', parameters('app').id)]", - "version": "[parameters('version')]" - } - }, - "resources": [] - } - }, - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", - "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" }, "resources": { - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", - "properties": { - "name": "[parameters('app').storage]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "storageAccount" - ] - }, - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", - "properties": { - "name": "[parameters('app').keyVault]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "keyVault" - ] - }, - "dataFactory::managedVirtualNetwork": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "properties": {}, - "dependsOn": [ - "dataFactory" - ] - }, - "dataFactory::managedIntegrationRuntime": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", - "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "default", - "type": "ManagedVirtualNetworkReference" - }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('app').hub.location]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 - } - } - } - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedVirtualNetwork" - ] - }, - "dataFactory::linkedService_keyVault": { - "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "keyVault" - ] - }, - "dataFactory::linkedService_storageAccount": { - "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "storageAccount" - ] + "publicAccess": "None", + "metadata": {} + } }, "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", + "existing": true, "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] - }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] - }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] - }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] - }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] - }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" - } - } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] - }, - "appTelemetry": { - "condition": "[parameters('app').hub.options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", - "properties": "[variables('telemetryProps')]" - }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" - } - } - }, - "storageRoleAssignments": { - "copy": { - "name": "storageRoleAssignments", - "count": "[length(variables('factoryStorageRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "storageAccount" - ] - }, - "triggerManagerIdentity": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "dependsOn": [ - "dataFactory" - ] - }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "triggerManagerIdentity" - ] + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" }, "storageAccount": { - "condition": "[variables('usesStorage')]", + "existing": true, "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "location": "[parameters('app').hub.location]", - "sku": { - "name": "[parameters('app').hub.options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + "name": "[parameters('app').storage]" }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Identity', deployment().name)]", "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "blob" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" - }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", - "properties": { - "sku": { - "name": "[parameters('app').hub.options.keyVaultSku]", - "family": "A" - }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" - } - }, - "dependsOn": [ - "dataFactory" - ] - }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} - }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('app').keyVault)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.keyVault]" - }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] - }, - "dependsOn": [ - "keyVault" - ] - }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "expressionEvaluationOptions": { + "scope": "inner" }, "mode": "Incremental", "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } + "app": { + "value": "[parameters('app')]" }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } + "identityName": { + "value": "[format('{0}_blobManager', parameters('app').storage)]" }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", - "getStoragePrivateEndpointConnections", - "keyVault" - ] - }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + "roles": { + "value": [ + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" + ] } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" + "version": "0.36.177.2456", + "templateHash": "4534337491931150093" } }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "getKeyVaultPrivateEndpointConnections", - "keyVault" - ] - }, - "getStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", - "stopTriggers", - "storageAccount" - ] - }, - "approveStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "getStoragePrivateEndpointConnections", - "storageAccount" - ] - }, - "stopTriggers": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "arguments": { - "value": "-Stop" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('app').dataFactory]" - } - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", + "_1.HubRoutingProperties": { + "type": "object", "properties": { "networkId": { "type": "string" @@ -9739,9 +8484,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -9768,7 +8510,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -9802,21 +8543,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -9825,21 +8580,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -9847,513 +8603,152 @@ } } }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." + "description": "Required. FinOps hub app the identity is associated with." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Required. Name of the user assigned identity." } }, - "arguments": { + "roleAssignmentResourceId": { "type": "string", - "defaultValue": "", "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." + "description": "Required. Resource ID of the resource access is being granted for." } }, - "environmentVariables": { + "roles": { "type": "array", "items": { - "$ref": "#/definitions/EnvironmentVariable" + "type": "string" }, - "defaultValue": [], "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "description": "Required. List of RBAC role assignment GUIDs." } } }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, "resources": { "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", "location": "[parameters('app').hub.location]" }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" + "count": "[length(parameters('roles'))]" }, - "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] + } + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "value": "[parameters('identityName')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" } } } - }, - "dependsOn": [ - "appTelemetry", - "dataFactory", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" - ] - } - }, - "outputs": { - "dataFactoryId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Factory instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" - }, - "keyVaultId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Key Vault instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" - }, - "storageAccountId": { - "type": "string", - "metadata": { - "description": "Resource ID of the storage account instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + } }, - "triggerManagerIdentityName": { - "type": "string", - "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." - }, - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - } - } - } - } - }, - "schemaFiles": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.Exports_Storage.SchemaFiles", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "container": { - "value": "config" - }, - "files": { - "value": { - "schemas/actualcost_c360-2025-04.json": "[variables('$fxv#0')]", - "schemas/amortizedcost_c360-2025-04.json": "[variables('$fxv#1')]", - "schemas/focuscost_1.2.json": "[variables('$fxv#2')]", - "schemas/focuscost_1.2-preview.json": "[variables('$fxv#3')]", - "schemas/focuscost_1.0r2.json": "[variables('$fxv#4')]", - "schemas/focuscost_1.0.json": "[variables('$fxv#5')]", - "schemas/focuscost_1.0-preview(v1).json": "[variables('$fxv#6')]", - "schemas/pricesheet_2023-05-01_ea.json": "[variables('$fxv#7')]", - "schemas/pricesheet_2023-05-01_mca.json": "[variables('$fxv#8')]", - "schemas/reservationdetails_2023-03-01.json": "[variables('$fxv#9')]", - "schemas/reservationrecommendations_2023-05-01_ea.json": "[variables('$fxv#10')]", - "schemas/reservationrecommendations_2023-05-01_mca.json": "[variables('$fxv#11')]", - "schemas/reservationtransactions_2023-05-01_ea.json": "[variables('$fxv#12')]", - "schemas/reservationtransactions_2023-05-01_mca.json": "[variables('$fxv#13')]" - } - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "7314877606184110283" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", + "uploadFiles": { + "condition": "[variables('hasFiles')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Upload', deployment().name)]", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" + "expressionEvaluationOptions": { + "scope": "inner" }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." - } - }, - "container": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage container to create or update." - } - }, - "files": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." - } - }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" - }, - "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", - "properties": { - "publicAccess": "None", - "metadata": {} - } - }, - "storageAccount::blobService": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" - }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Identity', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "roles": { - "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" - ] - } + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[reference('identity').outputs.name.value]" + }, + "environmentVariables": { + "value": [ + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" + }, + { + "name": "files", + "value": "[string(parameters('files'))]" + } + ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -10362,12 +8757,23 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2980528181281411934" + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } }, "definitions": { - "_1.HubProperties": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { "type": "object", "properties": { "id": { @@ -10485,9 +8891,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -10514,7 +8917,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -10548,21 +8950,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -10571,21 +8987,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -10593,13354 +9010,9051 @@ } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the identity is associated with." + "description": "Required. FinOps hub app the deployment script is being run for." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the user assigned identity." + "description": "Required. Name of the managed identity to create." } }, - "roleAssignmentResourceId": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." } }, - "roles": { + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/EnvironmentVariable" }, + "defaultValue": [], "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." + "description": "Optional. Environment variables to use for the deployment script." } } }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, "resources": { "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", "location": "[parameters('app').hub.location]" }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" + "count": "[length(variables('privateEndpointDeploymentRoles'))]" }, + "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } }, - "value": "[reference('identity').principalId]" + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } - } + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Upload', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "containerName": { - "type": "string", - "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" - }, - "filesUploaded": { - "type": "int", - "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" - }, - "identityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" - }, - "identityPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" - } - } - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "exportContainer": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.Exports_Storage.ExportContainer", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "container": { - "value": "[variables('MSEXPORTS')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "7314877606184110283" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." - } - }, - "container": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage container to create or update." - } - }, - "files": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." - } - }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" - }, - "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", - "properties": { - "publicAccess": "None", - "metadata": {} - } - }, - "storageAccount::blobService": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" - }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Identity', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "roles": { - "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2980528181281411934" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the identity is associated with." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the user assigned identity." - } - }, - "roleAssignmentResourceId": { - "type": "string", - "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." - } - }, - "roles": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." - } - } - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", - "location": "[parameters('app').hub.location]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." - }, - "value": "[reference('identity').principalId]" - } - } - } - } - }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Upload', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "containerName": { - "type": "string", - "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" - }, - "filesUploaded": { - "type": "int", - "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" - }, - "identityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" - }, - "identityPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" - } - } - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "trigger_ExportManifestAdded": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.Exports_ADF.ExportManifestTrigger", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('app').dataFactory]" - }, - "triggerName": { - "value": "[format('{0}_ManifestAdded', variables('MSEXPORTS'))]" - }, - "pipelineName": { - "value": "[format('{0}_ExecuteETL', variables('MSEXPORTS'))]" - }, - "pipelineParameters": { - "value": { - "folderPath": "@triggerBody().folderPath", - "fileName": "@triggerBody().fileName" - } - }, - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "storageContainer": { - "value": "[variables('MSEXPORTS')]" - }, - "storagePathEndsWith": { - "value": "manifest.json" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "14264521107451792604" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } - }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." - } - }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." - } - }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." - } - }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." - } - }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." - } - } - }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" - }, - "parameters": "[parameters('pipelineParameters')]" - } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] - } - } - } - ] - } - }, - "dependsOn": [ - "appRegistration", - "dataFactory::pipeline_ExecuteExportsETL" - ] - } - }, - "outputs": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Properties of the hub app." - }, - "value": "[parameters('app')]" - }, - "exportContainer": { - "type": "string", - "metadata": { - "description": "Name of the container used for Cost Management exports." - }, - "value": "[reference('exportContainer').outputs.containerName.value]" - }, - "schemaFilesUploaded": { - "type": "int", - "metadata": { - "description": "Number of schema files uploaded." - }, - "value": "[reference('schemaFiles').outputs.filesUploaded.value]" - } - } - } - }, - "dependsOn": [ - "core" - ] - }, - "cmManagedExports": { - "condition": "[parameters('enableManagedExports')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.ManagedExports", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft.CostManagement', 'ManagedExports')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "15949887161767442453" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getExportBody": { - "parameters": [ - { - "type": "string", - "name": "exportContainerName" - }, - { - "type": "string", - "name": "datasetType" - }, - { - "type": "string", - "name": "schemaVersion" - }, - { - "type": "bool", - "name": "isMonthly" - }, - { - "type": "string", - "name": "exportFormat" - }, - { - "type": "string", - "name": "compressionMode" - }, - { - "type": "string", - "name": "partitionData" - }, - { - "type": "string", - "name": "dataOverwriteBehavior" - } - ], - "output": { - "type": "string", - "value": "[format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}\", \"name\": \"@{{variables(''exportName'')}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'))]" - } - }, - "getExportBodyV2": { - "parameters": [ - { - "type": "string", - "name": "exportContainerName" - }, - { - "type": "string", - "name": "datasetType" - }, - { - "type": "bool", - "name": "isMonthly" - }, - { - "type": "string", - "name": "exportFormat" - }, - { - "type": "string", - "name": "compressionMode" - }, - { - "type": "string", - "name": "partitionData" - }, - { - "type": "string", - "name": "dataOverwriteBehavior" - }, - { - "type": "string", - "name": "recommendationScope" - }, - { - "type": "string", - "name": "recommendationLookbackPeriod" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "string", - "value": "[if(equals(toLower(parameters('datasetType')), 'focuscost'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{10}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), if(equals(toLower(parameters('datasetType')), 'reservationdetails'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(or(equals(toLower(parameters('datasetType')), 'pricesheet'), equals(toLower(parameters('datasetType')), 'reservationtransactions')), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}}}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheCurrentMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(equals(toLower(parameters('datasetType')), 'reservationrecommendations'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [ {{ \"name\": \"reservationScope\", \"value\": \"{1}\" }}, {{ \"name\": \"resourceType\", \"value\": \"{2}\" }}, {{ \"name\": \"lookBackPeriod\", \"value\": \"{3}\" }}] }}}}, \"timeframe\": \"{4}\", \"type\": \"{5}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{6}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{7}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{8}\", \"partitionData\": \"{9}\", \"dataOverwriteBehavior\": \"{10}\", \"compressionMode\": \"{11}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{12}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{13}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], parameters('recommendationScope'), parameters('resourceType'), parameters('recommendationLookbackPeriod'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), 'undefined'))))]" - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - } - }, - "variables": { - "CONFIG": "config", - "MSEXPORTS": "msexports", - "exportsApiVersion": "2023-07-01-preview", - "exportDataVersions": { - "focuscost": "1.2-preview", - "pricesheet": "2023-03-01", - "reservationdetails": "2023-03-01", - "reservationrecommendations": "2023-05-01", - "reservationtransactions": "2023-05-01" - }, - "finOpsToolkitVersion": "12.0" - }, - "resources": { - "dataFactory::dataset_config": { - "existing": true, - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]" - }, - "dataFactory::trigger_DailySchedule": { - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_DailySchedule', variables('CONFIG')))]", - "properties": { - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[format('{0}_StartExportProcess', variables('CONFIG'))]", - "type": "PipelineReference" - }, - "parameters": { - "Recurrence": "Daily" - } - } - ], - "type": "ScheduleTrigger", - "typeProperties": { - "recurrence": { - "frequency": "Hour", - "interval": 24, - "startTime": "2023-01-01T01:01:00", - "timeZone": "[reference('timeZones').outputs.Timezone.value]" - } - } - }, - "dependsOn": [ - "dataFactory::pipeline_StartExportProcess", - "timeZones" - ] - }, - "dataFactory::trigger_MonthlySchedule": { - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_MonthlySchedule', variables('CONFIG')))]", - "properties": { - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[format('{0}_StartExportProcess', variables('CONFIG'))]", - "type": "PipelineReference" - }, - "parameters": { - "Recurrence": "Monthly" - } - } - ], - "type": "ScheduleTrigger", - "typeProperties": { - "recurrence": { - "frequency": "Month", - "interval": 1, - "startTime": "2023-01-05T01:11:00", - "timeZone": "[reference('timeZones').outputs.Timezone.value]", - "schedule": { - "monthDays": [ - 2, - 5, - 19 - ] - } - } - } - }, - "dependsOn": [ - "dataFactory::pipeline_StartExportProcess", - "timeZones" - ] - }, - "dataFactory::pipeline_StartBackfillProcess": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_StartBackfillProcess', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('fileName')", - "type": "Expression" - }, - "folderPath": { - "value": "@variables('folderPath')", - "type": "Expression" - } - } - } - } - }, - { - "name": "Set backfill end date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "endDate", - "value": { - "value": "@addDays(startOfMonth(utcNow()), -1)", - "type": "Expression" - } - } - }, - { - "name": "Set backfill start date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "startDate", - "value": { - "value": "@subtractFromTime(startOfMonth(utcNow()), activity('Get Config').output.firstRow.retention.ingestion.months, 'Month')", - "type": "Expression" - } - } - }, - { - "name": "Set export start date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set backfill start date", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "thisMonth", - "value": { - "value": "@startOfMonth(variables('endDate'))", - "type": "Expression" - } - } - }, - { - "name": "Set export end date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set export start date", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "nextMonth", - "value": { - "value": "@startOfMonth(subtractFromTime(variables('thisMonth'), 1, 'Month'))", - "type": "Expression" - } - } - }, - { - "name": "Every Month", - "type": "Until", - "dependsOn": [ - { - "activity": "Set export end date", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set backfill end date", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@less(variables('thisMonth'), variables('startDate'))", - "type": "Expression" - }, - "activities": [ - { - "name": "Update export start date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Backfill data", - "dependencyConditions": [ - "Completed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "thisMonth", - "value": { - "value": "@variables('nextMonth')", - "type": "Expression" - } - } - }, - { - "name": "Update export end date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Update export start date", - "dependencyConditions": [ - "Completed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "nextMonth", - "value": { - "value": "@subtractFromTime(variables('thisMonth'), 1, 'Month')", - "type": "Expression" - } - } - }, - { - "name": "Backfill data", - "type": "ExecutePipeline", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_RunBackfillJob', variables('CONFIG'))]", - "type": "PipelineReference" - }, - "waitOnCompletion": true, - "parameters": { - "StartDate": { - "value": "@variables('thisMonth')", - "type": "Expression" - }, - "EndDate": { - "value": "@addDays(addToTime(variables('thisMonth'), 1, 'Month'), -1)", - "type": "Expression" - } - } - } - } - ], - "timeout": "0.02:00:00" - } - } - ], - "concurrency": 1, - "variables": { - "exportName": { - "type": "String" - }, - "storageAccountId": { - "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "finOpsHub": { - "type": "String", - "defaultValue": "[parameters('app').hub.name]" - }, - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[variables('CONFIG')]" - }, - "endDate": { - "type": "String" - }, - "startDate": { - "type": "String" - }, - "thisMonth": { - "type": "String" - }, - "nextMonth": { - "type": "String" - } - } - }, - "dependsOn": [ - "dataFactory::pipeline_RunBackfillJob" - ] - }, - "dataFactory::pipeline_RunBackfillJob": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_RunBackfillJob', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('fileName')", - "type": "Expression" - }, - "folderPath": { - "value": "@variables('folderPath')", - "type": "Expression" - } - } - } - } - }, - { - "name": "Set Scopes", - "description": "Save scopes to test if it is an array", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@activity('Get Config').output.firstRow.scopes", - "type": "Expression" - } - } - }, - { - "name": "Set Scopes as Array", - "description": "Wraps a single scope object into an array to work around the PowerShell bug where single-item arrays are sometimes written as a single object instead of an array.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@createArray(activity('Get Config').output.firstRow.scopes)", - "type": "Expression" - } - } - }, - { - "name": "Filter Invalid Scopes", - "description": "Remove any invalid scopes to avoid errors.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Succeeded", - "Failed" - ] - }, - { - "activity": "Set Scopes as Array", - "dependencyConditions": [ - "Skipped", - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@variables('scopesArray')", - "type": "Expression" - }, - "condition": { - "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", - "type": "Expression" - } - } - }, - { - "name": "ForEach Export Scope", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Filter Invalid Scopes", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@activity('Filter Invalid Scopes').output.Value", - "type": "Expression" - }, - "isSequential": true, - "activities": [ - { - "name": "Set backfill export name", - "type": "SetVariable", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "variableName": "exportName", - "value": { - "value": "@toLower(concat(variables('finOpsHub'), '-monthly-costdetails'))", - "type": "Expression" - } - } - }, - { - "name": "Trigger backfill export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Set backfill export name", - "dependencyConditions": [ - "Completed" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 1, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}/run?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "POST", - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunBackfill@{0}', variables('finOpsToolkitVersion'))]", - "Content-Type": "application/json", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "body": "{\"timePeriod\" : { \"from\" : \"@{pipeline().parameters.StartDate}\", \"to\" : \"@{pipeline().parameters.EndDate}\" }}", - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - } - ] - } - } - ], - "concurrency": 1, - "parameters": { - "StartDate": { - "type": "string" - }, - "EndDate": { - "type": "string" - } - }, - "variables": { - "exportName": { - "type": "String" - }, - "storageAccountId": { - "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "finOpsHub": { - "type": "String", - "defaultValue": "[parameters('app').hub.name]" - }, - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[variables('CONFIG')]" - }, - "scopesArray": { - "type": "Array" - } - } - } - }, - "dataFactory::pipeline_StartExportProcess": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_StartExportProcess', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('fileName')", - "type": "Expression" - }, - "folderPath": { - "value": "@variables('folderPath')", - "type": "Expression" - } - } - } - } - }, - { - "name": "Set Scopes", - "description": "Save scopes to test if it is an array", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@activity('Get Config').output.firstRow.scopes", - "type": "Expression" - } - } - }, - { - "name": "Set Scopes as Array", - "description": "Wraps a single scope object into an array to work around the PowerShell bug where single-item arrays are sometimes written as a single object instead of an array.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@createArray(activity('Get Config').output.firstRow.scopes)", - "type": "Expression" - } - } - }, - { - "name": "Filter Invalid Scopes", - "description": "Remove any invalid scopes to avoid errors.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Succeeded", - "Failed" - ] - }, - { - "activity": "Set Scopes as Array", - "dependencyConditions": [ - "Succeeded", - "Skipped" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@variables('scopesArray')", - "type": "Expression" - }, - "condition": { - "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", - "type": "Expression" - } - } - }, - { - "name": "ForEach Export Scope", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Filter Invalid Scopes", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@activity('Filter Invalid Scopes').output.Value", - "type": "Expression" - }, - "isSequential": true, - "activities": [ - { - "name": "Get exports for scope", - "type": "WebActivity", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "GET", - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Run exports for scope", - "type": "ExecutePipeline", - "dependsOn": [ - { - "activity": "Get exports for scope", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_RunExportJobs', variables('CONFIG'))]", - "type": "PipelineReference" - }, - "waitOnCompletion": true, - "parameters": { - "ExportScopes": { - "value": "@activity('Get exports for scope').output.value", - "type": "Expression" - }, - "Recurrence": { - "value": "@pipeline().parameters.Recurrence", - "type": "Expression" - } - } - } - } - ] - } - } - ], - "concurrency": 1, - "parameters": { - "Recurrence": { - "type": "string", - "defaultValue": "Daily" - } - }, - "variables": { - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[variables('CONFIG')]" - }, - "finOpsHub": { - "type": "String", - "defaultValue": "[parameters('app').hub.name]" - }, - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "scopesArray": { - "type": "Array" - } - } - }, - "dependsOn": [ - "dataFactory::pipeline_RunExportJobs" - ] - }, - "dataFactory::pipeline_RunExportJobs": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_RunExportJobs', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "ForEach export scope", - "type": "ForEach", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@pipeline().parameters.exportScopes", - "type": "Expression" - }, - "isSequential": true, - "activities": [ - { - "name": "If scheduled", - "type": "IfCondition", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@and( startswith(toLower(item().name), toLower(variables('hubName'))), and(contains(string(item().properties.schedule), 'recurrence'), equals(toLower(item().properties.schedule.recurrence), toLower(pipeline().parameters.Recurrence))))", - "type": "Expression" - }, - "ifTrueActivities": [ - { - "name": "Trigger export", - "type": "WebActivity", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "method": "POST", - "url": { - "value": "[format('@{{replace(toLower(concat(variables(''resourceManagementUri''),item().id)), ''com//'', ''com/'')}}/run?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "body": " ", - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - } - ] - } - } - ] - } - } - ], - "concurrency": 1, - "parameters": { - "ExportScopes": { - "type": "array" - }, - "Recurrence": { - "type": "string", - "defaultValue": "Daily" - } - }, - "variables": { - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "hubName": { - "type": "String", - "defaultValue": "[parameters('app').hub.name]" - } - } - }, - "dependsOn": [ - "dataFactory::dataset_config" - ] - }, - "dataFactory::pipeline_ConfigureExports": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ConfigureExports', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('fileName')", - "type": "Expression" - }, - "folderPath": { - "value": "@variables('folderPath')", - "type": "Expression" - } - } - } - } - }, - { - "name": "Save Scopes", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@activity('Get Config').output.firstRow.scopes", - "type": "Expression" - } - } - }, - { - "name": "Save Scopes as Array", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Save Scopes", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@array(activity('Get Config').output.firstRow.scopes)", - "type": "Expression" - } - } - }, - { - "name": "Filter Invalid Scopes", - "type": "Filter", - "dependsOn": [ - { - "activity": "Save Scopes", - "dependencyConditions": [ - "Succeeded", - "Failed" - ] - }, - { - "activity": "Save Scopes as Array", - "dependencyConditions": [ - "Skipped", - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@variables('scopesArray')", - "type": "Expression" - }, - "condition": { - "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", - "type": "Expression" - } - } - }, - { - "name": "ForEach Export Scope", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Filter Invalid Scopes", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@activity('Filter Invalid Scopes').output.value", - "type": "Expression" - }, - "isSequential": true, - "activities": [ - { - "name": "Set Export Type", - "type": "SetVariable", - "dependsOn": [], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "exportScopeType", - "value": { - "value": "@if(contains(toLower(item().scope), 'providers/microsoft.billing/billingaccounts'), if(contains(toLower(item().scope), ':'), 'mca', 'ea'), if(contains(toLower(item().scope), 'subscriptions/'), 'subscription', 'undefined'))", - "type": "Expression" - } - } - }, - { - "name": "Switch Export Type", - "type": "Switch", - "dependsOn": [ - { - "activity": "Set Export Type", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "on": { - "value": "@toLower(variables('exportScopeType'))", - "type": "Expression" - }, - "cases": [ - { - "value": "ea", - "activities": [ - { - "name": "Open month focus export", - "type": "WebActivity", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Closed month focus export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Open month focus export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Monthly pricesheet export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Closed month focus export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'Pricesheet', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Trigger EA monthly pricesheet export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Monthly pricesheet export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "method": "POST", - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}/run?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "body": " ", - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Daily reservation details export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Monthly pricesheet export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationDetails', false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationDetails@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Daily reservation transactions export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Daily reservation details export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationtransactions''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationTransactions', false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationTransactions@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Daily shared 30day virtual machines", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Daily reservation transactions export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-recommendations-shared-last30days-virtualmachines''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationRecommendations', false(), 'CSV', 'None', 'true', 'CreateNewReport', 'Shared', 'Last30Days', 'VirtualMachines')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationRecommendations.VM.Shared.30d@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - } - ] - }, - { - "value": "subscription", - "activities": [ - { - "name": "Subscription open month focus export", - "type": "WebActivity", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Subscription closed month focus export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Subscription open month focus export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - } - ] - }, - { - "value": "mca", - "activities": [ - { - "name": "Export Type Unsupported Error", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('MCA agreements are not supported for managed exports :',variables('exportScope'))", - "type": "Expression" - }, - "errorCode": "ExportTypeUnsupported" - } - } - ] - } - ], - "defaultActivities": [ - { - "name": "Export Type Not Defined Error", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to determine the export scope type for :',variables('exportScope'))", - "type": "Expression" - }, - "errorCode": "ExportTypeNotDefined" - } - } - ] - } - } - ] - } - } - ], - "concurrency": 1, - "variables": { - "scopesArray": { - "type": "Array" - }, - "exportName": { - "type": "String" - }, - "exportScope": { - "type": "String" - }, - "exportScopeType": { - "type": "String" - }, - "storageAccountId": { - "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "finOpsHub": { - "type": "String", - "defaultValue": "[parameters('app').hub.name]" - }, - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[variables('CONFIG')]" - } - } - } - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" - }, - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]" - }, - "appRegistration": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.ManagedExports_Register", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "version": { - "value": "[variables('finOpsToolkitVersion')]" - }, - "features": { - "value": [ - "DataFactory" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5436870138046688593" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppFeature": { - "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], - "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "version": { - "type": "string", - "metadata": { - "description": "Required. Version number of the FinOps hub app." - } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." - } - }, - "storageRoles": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." - } - }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0}', parameters('app').id)]", - "version": "[parameters('version')]" - } - }, - "resources": [] - } - }, - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", - "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" - }, - "resources": { - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", - "properties": { - "name": "[parameters('app').storage]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "storageAccount" - ] - }, - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", - "properties": { - "name": "[parameters('app').keyVault]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "keyVault" - ] - }, - "dataFactory::managedVirtualNetwork": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "properties": {}, - "dependsOn": [ - "dataFactory" - ] - }, - "dataFactory::managedIntegrationRuntime": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", - "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "default", - "type": "ManagedVirtualNetworkReference" - }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('app').hub.location]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 - } - } - } - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedVirtualNetwork" - ] - }, - "dataFactory::linkedService_keyVault": { - "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "keyVault" - ] - }, - "dataFactory::linkedService_storageAccount": { - "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "storageAccount" - ] - }, - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] - }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] - }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] - }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] - }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] - }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" - } - } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] - }, - "appTelemetry": { - "condition": "[parameters('app').hub.options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", - "properties": "[variables('telemetryProps')]" - }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" - } - } - }, - "storageRoleAssignments": { - "copy": { - "name": "storageRoleAssignments", - "count": "[length(variables('factoryStorageRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "storageAccount" - ] - }, - "triggerManagerIdentity": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "dependsOn": [ - "dataFactory" - ] - }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "triggerManagerIdentity" - ] - }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "location": "[parameters('app').hub.location]", - "sku": { - "name": "[parameters('app').hub.options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" - }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "blob" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" - }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", - "properties": { - "sku": { - "name": "[parameters('app').hub.options.keyVaultSku]", - "family": "A" - }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" - } - }, - "dependsOn": [ - "dataFactory" - ] - }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} - }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('app').keyVault)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.keyVault]" - }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] - }, - "dependsOn": [ - "keyVault" - ] - }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", - "getStoragePrivateEndpointConnections", - "keyVault" - ] - }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "getKeyVaultPrivateEndpointConnections", - "keyVault" - ] - }, - "getStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", - "stopTriggers", - "storageAccount" - ] - }, - "approveStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "getStoragePrivateEndpointConnections", - "storageAccount" - ] - }, - "stopTriggers": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "arguments": { - "value": "-Stop" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('app').dataFactory]" - } - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "appTelemetry", - "dataFactory", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" - ] - } - }, - "outputs": { - "dataFactoryId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Factory instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" - }, - "keyVaultId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Key Vault instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" - }, - "storageAccountId": { - "type": "string", - "metadata": { - "description": "Resource ID of the storage account instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - }, - "triggerManagerIdentityName": { - "type": "string", - "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." - }, - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - } - } - } - } - }, - "timeZones": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.ManagedExports_TimeZones", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('app').hub.location]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "6509457716792571662" - } - }, - "parameters": { - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." - } - }, - "timezoneobject": { - "type": "object", - "defaultValue": { - "australiaeast": "AUS Eastern Standard Time", - "australiacentral": "AUS Eastern Standard Time", - "australiacentral2": "AUS Eastern Standard Time", - "australiasoutheast": "AUS Eastern Standard Time", - "brazilsouth": "E. South America Standard Time", - "canadacentral": "Central Standard Time", - "canadaeast": "Eastern Standard Time", - "centralindia": "India Standard Time", - "centralus": "Central Standard Time", - "eastasia": "China Standard Time", - "eastus": "Eastern Standard Time", - "eastus2": "Eastern Standard Time", - "francecentral": "W. Europe Standard Time", - "germanynorth": "W. Europe Standard Time", - "germanywestcentral": "W. Europe Standard Time", - "japaneast": "Japan Standard Time", - "japanwest": "Japan Standard Time", - "koreacentral": "Korea Standard Time", - "koreasouth": "Korea Standard Time", - "northcentralus": "Central Standard Time", - "northeurope": "GMT Standard Time", - "norwayeast": "W. Europe Standard Time", - "norwaywest": "W. Europe Standard Time", - "southcentralus": "Central Standard Time", - "southindia": "India Standard Time", - "southeastasia": "Singapore Standard Time", - "switzerlandnorth": "W. Europe Standard Time", - "switzerlandwest": "W. Europe Standard Time", - "uksouth": "GMT Standard Time", - "ukwest": "GMT Standard Time", - "westcentralus": "Central Standard Time", - "westeurope": "W. Europe Standard Time", - "westindia": "India Standard Time", - "westus": "Pacific Standard Time", - "westus2": "Pacific Standard Time" - } - }, - "utchrs": { - "type": "string", - "defaultValue": "[utcNow('hh')]" - }, - "utcmins": { - "type": "string", - "defaultValue": "[utcNow('mm')]" - }, - "utcsecs": { - "type": "string", - "defaultValue": "[utcNow('ss')]" - } - }, - "variables": { - "loc": "[toLower(replace(parameters('location'), ' ', ''))]", - "timezone": "[coalesce(tryGet(parameters('timezoneobject'), variables('loc')), 'Universal Coordinated Time')]" - }, - "resources": [], - "outputs": { - "AzureRegion": { - "type": "string", - "value": "[parameters('location')]" - }, - "Timezone": { - "type": "string", - "value": "[variables('timezone')]" - }, - "UtcHours": { - "type": "string", - "value": "[parameters('utchrs')]" - }, - "UtcMinutes": { - "type": "string", - "value": "[parameters('utcmins')]" - }, - "UtcSeconds": { - "type": "string", - "value": "[parameters('utcsecs')]" - } - } - } - } - }, - "trigger_SettingsUpdated": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core_SettingsUpdatedTrigger", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('app').dataFactory]" - }, - "triggerName": { - "value": "[format('{0}_SettingsUpdated', variables('CONFIG'))]" - }, - "pipelineName": { - "value": "[format('{0}_ConfigureExports', variables('CONFIG'))]" - }, - "pipelineParameters": { - "value": {} - }, - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "storageContainer": { - "value": "[variables('CONFIG')]" - }, - "storagePathEndsWith": { - "value": "settings.json" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "14264521107451792604" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } - }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." - } - }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." - } - }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." - } - }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." - } - }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." - } - } - }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" - }, - "parameters": "[parameters('pipelineParameters')]" - } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] - } - } - } - ] - } - }, - "dependsOn": [ - "dataFactory::pipeline_ConfigureExports" - ] - } - } - } - }, - "dependsOn": [ - "cmExports" - ] - }, - "analytics": { - "condition": "[or(variables('useFabric'), variables('useAzureDataExplorer'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'Analytics')]" - }, - "fabricQueryUri": { - "value": "[parameters('fabricQueryUri')]" - }, - "fabricCapacityUnits": { - "value": "[parameters('fabricCapacityUnits')]" - }, - "clusterName": { - "value": "[parameters('dataExplorerName')]" - }, - "clusterSku": { - "value": "[parameters('dataExplorerSku')]" - }, - "clusterCapacity": { - "value": "[parameters('dataExplorerCapacity')]" - }, - "rawRetentionInDays": { - "value": "[parameters('dataExplorerRawRetentionInDays')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "16399190021391778181" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "privateRoutingForLinkedServices": { - "parameters": [ - { - "$ref": "#/definitions/_1.HubProperties", - "name": "hub" - } - ], - "output": { - "type": "object", - "value": "[if(parameters('hub').options.privateRouting, createObject('connectVia', createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference')), createObject())]" - }, - "metadata": { - "description": "Returns an object that represents the properties needed to enable private routing for linked services. Use property expansion (`...value`) to apply to a linkedServices resource.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "clusterName": { - "type": "string", - "defaultValue": "", - "maxLength": 22, - "metadata": { - "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics. If empty, Azure Data Explorer will not be deployed. Required to use with Power BI if you have more than $2-5M/mo in costs being monitored. Default: \"\" (do not use)." - } - }, - "clusterSku": { - "type": "string", - "defaultValue": "Dev(No SLA)_Standard_E2a_v4", - "allowedValues": [ - "Dev(No SLA)_Standard_E2a_v4", - "Dev(No SLA)_Standard_D11_v2", - "Standard_D11_v2", - "Standard_D12_v2", - "Standard_D13_v2", - "Standard_D14_v2", - "Standard_D16d_v5", - "Standard_D32d_v4", - "Standard_D32d_v5", - "Standard_DS13_v2+1TB_PS", - "Standard_DS13_v2+2TB_PS", - "Standard_DS14_v2+3TB_PS", - "Standard_DS14_v2+4TB_PS", - "Standard_E2a_v4", - "Standard_E2ads_v5", - "Standard_E2d_v4", - "Standard_E2d_v5", - "Standard_E4a_v4", - "Standard_E4ads_v5", - "Standard_E4d_v4", - "Standard_E4d_v5", - "Standard_E8a_v4", - "Standard_E8ads_v5", - "Standard_E8as_v4+1TB_PS", - "Standard_E8as_v4+2TB_PS", - "Standard_E8as_v5+1TB_PS", - "Standard_E8as_v5+2TB_PS", - "Standard_E8d_v4", - "Standard_E8d_v5", - "Standard_E8s_v4+1TB_PS", - "Standard_E8s_v4+2TB_PS", - "Standard_E8s_v5+1TB_PS", - "Standard_E8s_v5+2TB_PS", - "Standard_E16a_v4", - "Standard_E16ads_v5", - "Standard_E16as_v4+3TB_PS", - "Standard_E16as_v4+4TB_PS", - "Standard_E16as_v5+3TB_PS", - "Standard_E16as_v5+4TB_PS", - "Standard_E16d_v4", - "Standard_E16d_v5", - "Standard_E16s_v4+3TB_PS", - "Standard_E16s_v4+4TB_PS", - "Standard_E16s_v5+3TB_PS", - "Standard_E16s_v5+4TB_PS", - "Standard_E64i_v3", - "Standard_E80ids_v4", - "Standard_EC8ads_v5", - "Standard_EC8as_v5+1TB_PS", - "Standard_EC8as_v5+2TB_PS", - "Standard_EC16ads_v5", - "Standard_EC16as_v5+3TB_PS", - "Standard_EC16as_v5+4TB_PS", - "Standard_L4s", - "Standard_L8as_v3", - "Standard_L8s", - "Standard_L8s_v2", - "Standard_L8s_v3", - "Standard_L16as_v3", - "Standard_L16s", - "Standard_L16s_v2", - "Standard_L16s_v3", - "Standard_L32as_v3", - "Standard_L32s_v3" - ], - "metadata": { - "description": "Optional. Name of the Azure Data Explorer SKU. Default: \"Dev(No SLA)_Standard_E2a_v4\"." - } - }, - "clusterCapacity": { - "type": "int", - "defaultValue": 1, - "minValue": 1, - "maxValue": 1000, - "metadata": { - "description": "Optional. Number of nodes to use in the cluster. Allowed values: 1 for the Basic SKU tier and 2-1000 for Standard. Default: 1 for dev/test SKUs, 2 for standard SKUs." - } - }, - "fabricQueryUri": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Microsoft Fabric eventhouse query URI. Default: \"\" (do not use)." - } - }, - "fabricCapacityUnits": { - "type": "int", - "defaultValue": 2, - "minValue": 1, - "maxValue": 2048, - "metadata": { - "description": "Optional. Number of capacity units for the Microsoft Fabric capacity. This is the number in your Fabric SKU (e.g., Trial = 1, F2 = 2, F64 = 64). This is used to manage parallelization in data pipelines. If you change capacity, please redeploy the template. Allowed values: 1 for the Fabric trial and 2-2048 based on the assigned Fabric capacity (e.g., F2-F2048). Default: 2." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." - } - }, - "rawRetentionInDays": { - "type": "int", - "metadata": { - "description": "Required. Number of days of data to retain in the Data Explorer *_raw tables." - } - } - }, - "variables": { - "$fxv#0": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_1(id: string) {\n dynamic({\n \"arizeai.observabilityeval/organizations\": { \"SingularDisplayName\": \"Azure Native Arize AI Cloud Service\" }\n ,\"astronomer.astro/organizations\": { \"SingularDisplayName\": \"Astro Organization\" }\n ,\"citrix.services/xenappessentials\": { \"SingularDisplayName\": \"Citrix Virtual Apps Essentials\" }\n ,\"citrix.services/xendesktopessentials\": { \"SingularDisplayName\": \"Citrix Virtual Desktops Essentials\" }\n ,\"commvault.contentstore/cloudaccounts\": { \"SingularDisplayName\": \"Commvault Cloud Account\" }\n ,\"commvault.contentstore/cloudaccounts/plans\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts plan\" }\n ,\"commvault.contentstore/cloudaccounts/protectiongroups\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection group\" }\n ,\"commvault.contentstore/cloudaccounts/protectiongroups/protecteditems\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection groups protected item\" }\n ,\"commvault.contentstore/cloudaccounts/storages\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts storage\" }\n ,\"dell.storage/filesystems\": { \"SingularDisplayName\": \"Dell PowerScale\" }\n ,\"dynatrace.observability/monitors\": { \"SingularDisplayName\": \"Dynatrace\" }\n ,\"github.network/networksettings\": { \"SingularDisplayName\": \"GitHub.Network network setting\" }\n ,\"informatica.datamanagement/organizations\": { \"SingularDisplayName\": \"Informatica Organization\" }\n ,\"lambdatest.hyperexecute/organizations\": { \"SingularDisplayName\": \"Azure Native LambdaTest - HyperExecute Cloud Service\" }\n ,\"microsoft.aad/domainservices\": { \"SingularDisplayName\": \"Microsoft Entra Domain Services\" }\n ,\"microsoft.aadiam/diagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.aadiam diagnostic setting\" }\n ,\"microsoft.aadiam/privatelinkforazuread\": { \"SingularDisplayName\": \"Private Link for Microsoft Entra ID\" }\n ,\"microsoft.advisor/advisorscore\": { \"SingularDisplayName\": \"Microsoft.Advisor advisor score\" }\n ,\"microsoft.advisor/assessments\": { \"SingularDisplayName\": \"Microsoft.Advisor assessment\" }\n ,\"microsoft.advisor/configurations\": { \"SingularDisplayName\": \"Microsoft.Advisor configuration\" }\n ,\"microsoft.advisor/generaterecommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor generate recommendation\" }\n ,\"microsoft.advisor/metadata\": { \"SingularDisplayName\": \"Microsoft.Advisor metadata\" }\n ,\"microsoft.advisor/recommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendation\" }\n ,\"microsoft.advisor/recommendations/suppressions\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendations suppression\" }\n ,\"microsoft.advisor/resiliencyreviews\": { \"SingularDisplayName\": \"Microsoft.Advisor resiliency review\" }\n ,\"microsoft.agfoodplatform/farmbeats\": { \"SingularDisplayName\": \"Azure Data Manager for Agriculture\" }\n ,\"microsoft.agfoodplatform/farmbeatsextensiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats extension definition\" }\n ,\"microsoft.agfoodplatform/farmbeatssolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats solution definition\" }\n ,\"microsoft.agricultureplatform/agriservices\": { \"SingularDisplayName\": \"Agriculture data solutions\" }\n ,\"microsoft.akshybrid/agentpools\": { \"SingularDisplayName\": \"Microsoft.AksHybrid agent pool\" }\n ,\"microsoft.akshybrid/provisionedclusters\": { \"SingularDisplayName\": \"Microsoft.AksHybrid provisioned cluster\" }\n ,\"microsoft.akshybrid/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.AksHybrid upgrade profile\" }\n ,\"microsoft.alertsmanagement/actionrules\": { \"SingularDisplayName\": \"Alert processing rule\" }\n ,\"microsoft.alertsmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alert\" }\n ,\"microsoft.alertsmanagement/alerts/enrichments\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alerts enrichment\" }\n ,\"microsoft.alertsmanagement/prometheusrulegroups\": { \"SingularDisplayName\": \"Prometheus rule group\" }\n ,\"microsoft.alertsmanagement/smartdetectoralertrules\": { \"SingularDisplayName\": \"Smart detector alert rule\" }\n ,\"microsoft.alertsmanagement/smartgroups\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement smart group\" }\n ,\"microsoft.alertsmanagement/tenantactivitylogalerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement tenant activity log alert\" }\n ,\"microsoft.all/arcvirtualmachines\": { \"SingularDisplayName\": \"Azure Arc virtual machine\" }\n ,\"microsoft.all/hcivirtualmachines\": { \"SingularDisplayName\": \"Azure Local Virtual Machine - Azure Arc\" }\n ,\"microsoft.all/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.analysisservices/servers\": { \"SingularDisplayName\": \"Analysis Services server\" }\n ,\"microsoft.anybuild/clusters\": { \"SingularDisplayName\": \"AnyBuild cluster\" }\n ,\"microsoft.apicenter/deletedservices\": { \"SingularDisplayName\": \"Microsoft.ApiCenter deleted service\" }\n ,\"microsoft.apicenter/services\": { \"SingularDisplayName\": \"API Center\" }\n ,\"microsoft.apicenter/services/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.apimanagement/gateways\": { \"SingularDisplayName\": \"API Management gateway\" }\n ,\"microsoft.apimanagement/gateways/configconnections\": { \"SingularDisplayName\": \"Microsoft.ApiManagement gateways config connection\" }\n ,\"microsoft.apimanagement/service\": { \"SingularDisplayName\": \"API Management service\" }\n ,\"microsoft.apimanagement/service/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.apisecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.ApiSecurity defender setting\" }\n ,\"microsoft.app/agents\": { \"SingularDisplayName\": \"SRE Agent\" }\n ,\"microsoft.app/builders\": { \"SingularDisplayName\": \"Microsoft.App builder\" }\n ,\"microsoft.app/builders/builds\": { \"SingularDisplayName\": \"Microsoft.App builders build\" }\n ,\"microsoft.app/connectedenvironments\": { \"SingularDisplayName\": \"Container Apps Connected Environment\" }\n ,\"microsoft.app/containerapps\": { \"SingularDisplayName\": \"Container App\" }\n ,\"microsoft.app/jobs\": { \"SingularDisplayName\": \"Container App Job\" }\n ,\"microsoft.app/logicapps\": { \"SingularDisplayName\": \"Logic app\" }\n ,\"microsoft.app/logicapps/workflows\": { \"SingularDisplayName\": \"Logic app workflow\" }\n ,\"microsoft.app/managedenvironments\": { \"SingularDisplayName\": \"Container Apps Environment\" }\n ,\"microsoft.app/sessionpools\": { \"SingularDisplayName\": \"Container App Session Pool\" }\n ,\"microsoft.app/spaces\": { \"SingularDisplayName\": \"App Space\" }\n ,\"microsoft.appassessment/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate project\" }\n ,\"microsoft.appassessment/migrateprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessment\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedapplications\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed application\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed machine\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/machinestoassess\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments machines to asses\" }\n ,\"microsoft.appassessment/migrateprojects/sites\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects site\" }\n ,\"microsoft.appassessment/migrateprojects/sites/applianceconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects sites appliance configuration\" }\n ,\"microsoft.appcomplianceautomation/reports\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation report\" }\n ,\"microsoft.appcomplianceautomation/reports/evidences\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports evidence\" }\n ,\"microsoft.appcomplianceautomation/reports/scopingconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports scoping configuration\" }\n ,\"microsoft.appcomplianceautomation/reports/snapshots\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshot\" }\n ,\"microsoft.appcomplianceautomation/reports/snapshots/controls\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshots control\" }\n ,\"microsoft.appcomplianceautomation/reports/webhooks\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports webhook\" }\n ,\"microsoft.appconfiguration/configurationstores\": { \"SingularDisplayName\": \"App Configuration\" }\n ,\"microsoft.applicationmigration/discoveryhubs\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hub\" }\n ,\"microsoft.applicationmigration/discoveryhubs/applications\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs application\" }\n ,\"microsoft.applicationmigration/discoveryhubs/applications/members\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs applications member\" }\n ,\"microsoft.applicationmigration/pgsqlsites\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsite\" }\n ,\"microsoft.applicationmigration/pgsqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites agent\" }\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqldatabases\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqldatabase\" }\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqlinstances\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqlinstance\" }\n ,\"microsoft.appplatform/spring\": { \"SingularDisplayName\": \"Azure Spring Apps\" }\n ,\"microsoft.appsecurity/appprotectmanagedrulesetmanifests\": { \"SingularDisplayName\": \"Microsoft.AppSecurity app protect managed rule set manifest\" }\n ,\"microsoft.appsecurity/policies\": { \"SingularDisplayName\": \"App Protect Policy\" }\n ,\"microsoft.arc/all\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\n ,\"microsoft.arc/allfairfax\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\n ,\"microsoft.arc/kubernetesresources\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\n ,\"microsoft.arc/kubernetesresourcesfairfax\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\n ,\"microsoft.arcnetworking/arcnwloadbalancers\": { \"SingularDisplayName\": \"Microsoft.ArcNetworking arc nw load balancer\" }\n ,\"microsoft.aszlabhardware/labservers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware labserver\" }\n ,\"microsoft.aszlabhardware/reservations\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservation\" }\n ,\"microsoft.aszlabhardware/reservations/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservations server\" }\n ,\"microsoft.aszlabhardware/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware server\" }\n ,\"microsoft.attestation/attestationproviders\": { \"SingularDisplayName\": \"Attestation provider\" }\n ,\"microsoft.authorization/accessreviewhistorydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review history definition\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definition\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instance\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances/decisions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instances decision\" }\n ,\"microsoft.authorization/accessreviewschedulesettings\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule setting\" }\n ,\"microsoft.authorization/datapolicymanifests\": { \"SingularDisplayName\": \"Microsoft.Authorization data policy manifest\" }\n ,\"microsoft.authorization/denyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization deny assignment\" }\n ,\"microsoft.authorization/locks\": { \"SingularDisplayName\": \"Microsoft.Authorization lock\" }\n ,\"microsoft.authorization/policyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization policy assignment\" }\n ,\"microsoft.authorization/policydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definition\" }\n ,\"microsoft.authorization/policydefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definitions version\" }\n ,\"microsoft.authorization/policyexemptions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy exemption\" }\n ,\"microsoft.authorization/policysetdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definition\" }\n ,\"microsoft.authorization/policysetdefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definitions version\" }\n ,\"microsoft.authorization/privatelinkassociations\": { \"SingularDisplayName\": \"Microsoft.Authorization private link association\" }\n ,\"microsoft.authorization/provideroperations\": { \"SingularDisplayName\": \"Microsoft.Authorization provider operation\" }\n ,\"microsoft.authorization/resourcemanagementprivatelinks\": { \"SingularDisplayName\": \"Resource management private link\" }\n ,\"microsoft.authorization/roleassignmentapprovals\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approval\" }\n ,\"microsoft.authorization/roleassignmentapprovals/stages\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approvals stage\" }\n ,\"microsoft.authorization/roleassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment\" }\n ,\"microsoft.authorization/roleassignmentscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule instance\" }\n ,\"microsoft.authorization/roleassignmentschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule request\" }\n ,\"microsoft.authorization/roleassignmentschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule\" }\n ,\"microsoft.authorization/roledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role definition\" }\n ,\"microsoft.authorization/roleeligibilityscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule instance\" }\n ,\"microsoft.authorization/roleeligibilityschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule request\" }\n ,\"microsoft.authorization/roleeligibilityschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule\" }\n ,\"microsoft.authorization/rolemanagementalertconfigurations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert configuration\" }\n ,\"microsoft.authorization/rolemanagementalertdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert definition\" }\n ,\"microsoft.authorization/rolemanagementalertoperations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert operation\" }\n ,\"microsoft.authorization/rolemanagementalerts\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert\" }\n ,\"microsoft.authorization/rolemanagementalerts/alertincidents\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alerts alert incident\" }\n ,\"microsoft.authorization/rolemanagementpolicies\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy\" }\n ,\"microsoft.authorization/rolemanagementpolicyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy assignment\" }\n ,\"microsoft.automanage/bestpractices\": { \"SingularDisplayName\": \"Microsoft.Automanage best practice\" }\n ,\"microsoft.automanage/bestpractices/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage best practices version\" }\n ,\"microsoft.automanage/configurationprofileassignments\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignment\" }\n ,\"microsoft.automanage/configurationprofileassignments/reports\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignments report\" }\n ,\"microsoft.automanage/configurationprofiles\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile\" }\n ,\"microsoft.automanage/configurationprofiles/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profiles version\" }\n ,\"microsoft.automanage/serviceprincipals\": { \"SingularDisplayName\": \"ServicePrincipals\" }\n ,\"microsoft.automation/automationaccounts\": { \"SingularDisplayName\": \"Automation account\" }\n ,\"microsoft.automation/automationaccounts/hybridrunbookworkergroups\": { \"SingularDisplayName\": \"Automation hybrid worker group\" }\n ,\"microsoft.automation/automationaccounts/runbooks\": { \"SingularDisplayName\": \"Automation runbook\" }\n ,\"microsoft.autonomousdevelopmentplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform account\" }\n ,\"microsoft.autonomousdevelopmentplatform/accounts/datapools\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform accounts data pool\" }\n ,\"microsoft.autonomousdevelopmentplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform workspace\" }\n ,\"microsoft.avs/privateclouds\": { \"SingularDisplayName\": \"Azure VMware Solution private cloud\" }\n ,\"microsoft.awsconnector/accessanalyzeranalyzers\": { \"SingularDisplayName\": \"Access Analyzer Analyzer\" }\n ,\"microsoft.awsconnector/acmcertificatesummaries\": { \"SingularDisplayName\": \"ACM Certificate Summary\" }\n ,\"microsoft.awsconnector/apigatewayrestapis\": { \"SingularDisplayName\": \"Api Gateway Rest Api\" }\n ,\"microsoft.awsconnector/apigatewaystages\": { \"SingularDisplayName\": \"Api Gateway Stage\" }\n ,\"microsoft.awsconnector/applicationautoscalingscalabletargets\": { \"SingularDisplayName\": \"Application Auto Scaling Scalable Target\" }\n ,\"microsoft.awsconnector/appsyncgraphqlapis\": { \"SingularDisplayName\": \"App Sync Graphql Api\" }\n ,\"microsoft.awsconnector/autoscalingautoscalinggroups\": { \"SingularDisplayName\": \"Auto Scaling Auto Scaling Group\" }\n ,\"microsoft.awsconnector/cloudformationstacks\": { \"SingularDisplayName\": \"Cloud Formation Stack\" }\n ,\"microsoft.awsconnector/cloudformationstacksets\": { \"SingularDisplayName\": \"Cloud Formation Stack Set\" }\n ,\"microsoft.awsconnector/cloudfrontdistributions\": { \"SingularDisplayName\": \"Cloud Front Distribution\" }\n ,\"microsoft.awsconnector/cloudtrailtrails\": { \"SingularDisplayName\": \"Cloud Trail Trail\" }\n ,\"microsoft.awsconnector/cloudwatchalarms\": { \"SingularDisplayName\": \"Cloud Watch Alarm\" }\n ,\"microsoft.awsconnector/codebuildprojects\": { \"SingularDisplayName\": \"Code Build Project\" }\n ,\"microsoft.awsconnector/codebuildsourcecredentialsinfos\": { \"SingularDisplayName\": \"Code Build Source Credentials Info\" }\n ,\"microsoft.awsconnector/configserviceconfigurationrecorders\": { \"SingularDisplayName\": \"Config Service Configuration Recorder\" }\n ,\"microsoft.awsconnector/configserviceconfigurationrecorderstatuses\": { \"SingularDisplayName\": \"Config Service Configuration Recorder Status\" }\n ,\"microsoft.awsconnector/configservicedeliverychannels\": { \"SingularDisplayName\": \"Config Service Delivery Channel\" }\n ,\"microsoft.awsconnector/databasemigrationservicereplicationinstances\": { \"SingularDisplayName\": \"Database Migration Service Replication Instance\" }\n ,\"microsoft.awsconnector/daxclusters\": { \"SingularDisplayName\": \"DAX Cluster\" }\n ,\"microsoft.awsconnector/dynamodbcontinuousbackupsdescriptions\": { \"SingularDisplayName\": \"Dynamo DB Continuous Backups Description\" }\n ,\"microsoft.awsconnector/dynamodbtables\": { \"SingularDisplayName\": \"Dynamo DB Table\" }\n ,\"microsoft.awsconnector/ec2accountattributes\": { \"SingularDisplayName\": \"EC2 Account Attribute\" }\n ,\"microsoft.awsconnector/ec2addresses\": { \"SingularDisplayName\": \"EC2 Address\" }\n ,\"microsoft.awsconnector/ec2flowlogs\": { \"SingularDisplayName\": \"EC2 Flow Log\" }\n ,\"microsoft.awsconnector/ec2images\": { \"SingularDisplayName\": \"EC2 Image\" }\n ,\"microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\n ,\"microsoft.awsconnector/ec2instancestatuses\": { \"SingularDisplayName\": \"EC2 Instance Status\" }\n ,\"microsoft.awsconnector/ec2ipams\": { \"SingularDisplayName\": \"EC2 Ipam\" }\n ,\"microsoft.awsconnector/ec2keypairs\": { \"SingularDisplayName\": \"EC2 Key Pair\" }\n ,\"microsoft.awsconnector/ec2networkacls\": { \"SingularDisplayName\": \"EC2 Network Acl\" }\n ,\"microsoft.awsconnector/ec2networkinterfaces\": { \"SingularDisplayName\": \"EC2 Network Interface\" }\n ,\"microsoft.awsconnector/ec2routetables\": { \"SingularDisplayName\": \"EC2 Route Table\" }\n ,\"microsoft.awsconnector/ec2securitygroups\": { \"SingularDisplayName\": \"EC2 Security Group\" }\n ,\"microsoft.awsconnector/ec2snapshots\": { \"SingularDisplayName\": \"EC2 Snapshot\" }\n ,\"microsoft.awsconnector/ec2subnets\": { \"SingularDisplayName\": \"EC2 Subnet\" }\n ,\"microsoft.awsconnector/ec2volumes\": { \"SingularDisplayName\": \"EC2 Volume\" }\n ,\"microsoft.awsconnector/ec2vpcendpoints\": { \"SingularDisplayName\": \"EC2 VPCEndpoint\" }\n ,\"microsoft.awsconnector/ec2vpcpeeringconnections\": { \"SingularDisplayName\": \"EC2 VPCPeering Connection\" }\n ,\"microsoft.awsconnector/ec2vpcs\": { \"SingularDisplayName\": \"EC2 VPC\" }\n ,\"microsoft.awsconnector/ecrimagedetails\": { \"SingularDisplayName\": \"ECR Image Detail\" }\n ,\"microsoft.awsconnector/ecrrepositories\": { \"SingularDisplayName\": \"ECR Repository\" }\n ,\"microsoft.awsconnector/ecsclusters\": { \"SingularDisplayName\": \"ECS Cluster\" }\n ,\"microsoft.awsconnector/ecsservices\": { \"SingularDisplayName\": \"ECS Service\" }\n ,\"microsoft.awsconnector/ecstaskdefinitions\": { \"SingularDisplayName\": \"ECS Task Definition\" }\n ,\"microsoft.awsconnector/efsfilesystems\": { \"SingularDisplayName\": \"EFS File System\" }\n ,\"microsoft.awsconnector/efsmounttargets\": { \"SingularDisplayName\": \"EFS Mount Target\" }\n ,\"microsoft.awsconnector/eksnodegroups\": { \"SingularDisplayName\": \"EKS Nodegroup\" }\n ,\"microsoft.awsconnector/elasticbeanstalkapplications\": { \"SingularDisplayName\": \"Elastic Beanstalk Application\" }\n ,\"microsoft.awsconnector/elasticbeanstalkconfigurationtemplates\": { \"SingularDisplayName\": \"Elastic Beanstalk Configuration Template\" }\n ,\"microsoft.awsconnector/elasticbeanstalkenvironments\": { \"SingularDisplayName\": \"Elastic Beanstalk Environment\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2listeners\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Listener\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2loadbalancers\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Load Balancer\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2targetgroups\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Target Group\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2targethealthdescriptions\": { \"SingularDisplayName\": \"Elastic Load Balancing v2 Target Health Description\" }\n ,\"microsoft.awsconnector/elasticsearchdomains\": { \"SingularDisplayName\": \"Elasticsearch Domain\" }\n ,\"microsoft.awsconnector/emrclusters\": { \"SingularDisplayName\": \"EMR Cluster\" }\n ,\"microsoft.awsconnector/guarddutydetectors\": { \"SingularDisplayName\": \"Guard Duty Detector\" }\n ,\"microsoft.awsconnector/iamaccesskeylastuseds\": { \"SingularDisplayName\": \"IAM Access Key Last Used\" }\n ,\"microsoft.awsconnector/iamaccesskeymetadata\": { \"SingularDisplayName\": \"IAM Access Key Metadata\" }\n ,\"microsoft.awsconnector/iamgroups\": { \"SingularDisplayName\": \"IAM Group\" }\n ,\"microsoft.awsconnector/iaminstanceprofiles\": { \"SingularDisplayName\": \"IAM Instance Profile\" }\n ,\"microsoft.awsconnector/iammanagedpolicies\": { \"SingularDisplayName\": \"IAM Managed Policy\" }\n ,\"microsoft.awsconnector/iammfadevices\": { \"SingularDisplayName\": \"IAM MFADevice\" }\n ,\"microsoft.awsconnector/iampasswordpolicies\": { \"SingularDisplayName\": \"IAM Password Policy\" }\n ,\"microsoft.awsconnector/iampolicyversions\": { \"SingularDisplayName\": \"IAM Policy Version\" }\n ,\"microsoft.awsconnector/iamroles\": { \"SingularDisplayName\": \"IAM Role\" }\n ,\"microsoft.awsconnector/iamservercertificates\": { \"SingularDisplayName\": \"IAM Server Certificate\" }\n ,\"microsoft.awsconnector/iamuserpolicies\": { \"SingularDisplayName\": \"IAM User Policy\" }\n ,\"microsoft.awsconnector/iamvirtualmfadevices\": { \"SingularDisplayName\": \"IAM Virtual MFADevice\" }\n ,\"microsoft.awsconnector/kmsaliases\": { \"SingularDisplayName\": \"KMS Alias\" }\n ,\"microsoft.awsconnector/kmskeys\": { \"SingularDisplayName\": \"KMS Key\" }\n ,\"microsoft.awsconnector/lambdafunctioncodelocations\": { \"SingularDisplayName\": \"Lambda Function Code Location\" }\n ,\"microsoft.awsconnector/lambdafunctionconfigurations\": { \"SingularDisplayName\": \"Microsoft.AwsConnector lambda function configuration\" }\n ,\"microsoft.awsconnector/lambdafunctions\": { \"SingularDisplayName\": \"Lambda Function\" }\n ,\"microsoft.awsconnector/licensemanagerlicenses\": { \"SingularDisplayName\": \"License Manager License\" }\n ,\"microsoft.awsconnector/lightsailbuckets\": { \"SingularDisplayName\": \"Lightsail Bucket\" }\n ,\"microsoft.awsconnector/lightsailinstances\": { \"SingularDisplayName\": \"Lightsail Instance\" }\n ,\"microsoft.awsconnector/logsloggroups\": { \"SingularDisplayName\": \"Logs Log Group\" }\n ,\"microsoft.awsconnector/logslogstreams\": { \"SingularDisplayName\": \"Logs Log Stream\" }\n ,\"microsoft.awsconnector/logsmetricfilters\": { \"SingularDisplayName\": \"Logs Metric Filter\" }\n ,\"microsoft.awsconnector/logssubscriptionfilters\": { \"SingularDisplayName\": \"Logs Subscription Filter\" }\n ,\"microsoft.awsconnector/macie2jobsummaries\": { \"SingularDisplayName\": \"Macie2 Job Summary\" }\n ,\"microsoft.awsconnector/macieallowlists\": { \"SingularDisplayName\": \"Macie Allow List\" }\n ,\"microsoft.awsconnector/networkfirewallfirewallpolicies\": { \"SingularDisplayName\": \"Network Firewall Firewall Policy\" }\n ,\"microsoft.awsconnector/networkfirewallfirewalls\": { \"SingularDisplayName\": \"Network Firewall Firewall\" }\n ,\"microsoft.awsconnector/networkfirewallrulegroups\": { \"SingularDisplayName\": \"Network Firewall Rule Group\" }\n ,\"microsoft.awsconnector/opensearchdomainstatuses\": { \"SingularDisplayName\": \"Open Search Domain Status\" }\n ,\"microsoft.awsconnector/opensearchservicedomains\": { \"SingularDisplayName\": \"Open Search Service Domain\" }\n ,\"microsoft.awsconnector/organizationsaccounts\": { \"SingularDisplayName\": \"Organizations Account\" }\n ,\"microsoft.awsconnector/organizationsorganizations\": { \"SingularDisplayName\": \"Organizations Organization\" }\n ,\"microsoft.awsconnector/rdsdbclusters\": { \"SingularDisplayName\": \"RDS DBCluster\" }\n ,\"microsoft.awsconnector/rdsdbinstances\": { \"SingularDisplayName\": \"RDS DBInstance\" }\n ,\"microsoft.awsconnector/rdsdbsnapshotattributesresults\": { \"SingularDisplayName\": \"RDS DBSnapshot Attributes Result\" }\n ,\"microsoft.awsconnector/rdsdbsnapshots\": { \"SingularDisplayName\": \"RDS DBSnapshot\" }\n ,\"microsoft.awsconnector/rdseventsubscriptions\": { \"SingularDisplayName\": \"RDS Event Subscription\" }\n ,\"microsoft.awsconnector/rdsexporttasks\": { \"SingularDisplayName\": \"RDS Export Task\" }\n ,\"microsoft.awsconnector/redshiftclusterparametergroups\": { \"SingularDisplayName\": \"Redshift Cluster Parameter Group\" }\n ,\"microsoft.awsconnector/redshiftclusters\": { \"SingularDisplayName\": \"Redshift Cluster\" }\n ,\"microsoft.awsconnector/route53domainsdomainsummaries\": { \"SingularDisplayName\": \"Route 53 Domains Domain Summary\" }\n ,\"microsoft.awsconnector/route53hostedzones\": { \"SingularDisplayName\": \"Route53 Hosted Zone\" }\n ,\"microsoft.awsconnector/route53resourcerecordsets\": { \"SingularDisplayName\": \"Route 53 Resource Record Set\" }\n ,\"microsoft.awsconnector/s3accesscontrolpolicies\": { \"SingularDisplayName\": \"S3 Access Control Policy\" }\n ,\"microsoft.awsconnector/s3accesspoints\": { \"SingularDisplayName\": \"S3 Access Point\" }\n ,\"microsoft.awsconnector/s3bucketpolicies\": { \"SingularDisplayName\": \"S3 Bucket Policy\" }\n ,\"microsoft.awsconnector/s3buckets\": { \"SingularDisplayName\": \"S3 Bucket\" }\n ,\"microsoft.awsconnector/s3controlmultiregionaccesspointpolicydocuments\": { \"SingularDisplayName\": \"S3 Control Multi Region Access Point Policy Document\" }\n ,\"microsoft.awsconnector/sagemakerapps\": { \"SingularDisplayName\": \"Sage Maker App\" }\n ,\"microsoft.awsconnector/sagemakerdevices\": { \"SingularDisplayName\": \"Sage Maker Device\" }\n ,\"microsoft.awsconnector/sagemakerimages\": { \"SingularDisplayName\": \"Sage Maker Image\" }\n ,\"microsoft.awsconnector/sagemakernotebookinstancesummaries\": { \"SingularDisplayName\": \"Sage Maker Notebook Instance Summary\" }\n ,\"microsoft.awsconnector/secretsmanagerresourcepolicies\": { \"SingularDisplayName\": \"Secrets Manager Resource Policy\" }\n ,\"microsoft.awsconnector/secretsmanagersecrets\": { \"SingularDisplayName\": \"Secrets Manager Secret\" }\n ,\"microsoft.awsconnector/snssubscriptions\": { \"SingularDisplayName\": \"SNS Subscription\" }\n ,\"microsoft.awsconnector/snstopics\": { \"SingularDisplayName\": \"SNS Topic\" }\n ,\"microsoft.awsconnector/sqsqueues\": { \"SingularDisplayName\": \"SQS Queue\" }\n ,\"microsoft.awsconnector/ssminstanceinformations\": { \"SingularDisplayName\": \"SSM Instance Information\" }\n ,\"microsoft.awsconnector/ssmparameters\": { \"SingularDisplayName\": \"SSM Parameter\" }\n ,\"microsoft.awsconnector/ssmresourcecompliancesummaryitems\": { \"SingularDisplayName\": \"SSM Resource Compliance Summary Item\" }\n ,\"microsoft.awsconnector/wafv2ipsets\": { \"SingularDisplayName\": \"WAFv2 IPSet\" }\n ,\"microsoft.awsconnector/wafv2loggingconfigurations\": { \"SingularDisplayName\": \"WAFv2 Logging Configuration\" }\n ,\"microsoft.awsconnector/wafv2webaclassociations\": { \"SingularDisplayName\": \"WAFv2 Web ACLAssociation\" }\n ,\"microsoft.awsconnector/wafwebaclsummaries\": { \"SingularDisplayName\": \"WAF Web ACLSummary\" }\n ,\"microsoft.azureactivedirectory/b2cdirectories\": { \"SingularDisplayName\": \"B2C tenant\" }\n ,\"microsoft.azureactivedirectory/ciamdirectories\": { \"SingularDisplayName\": \"External Configuration Tenant\" }\n ,\"microsoft.azureactivedirectory/guestusages\": { \"SingularDisplayName\": \"Guest Usage\" }\n ,\"microsoft.azurearcdata/datacontrollers\": { \"SingularDisplayName\": \"Azure Arc data controller\" }\n ,\"microsoft.azurearcdata/mysqlserver\": { \"SingularDisplayName\": \"MySql Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/postgresinstances\": { \"SingularDisplayName\": \"PostgreSQL server ? Azure Arc\" }\n ,\"microsoft.azurearcdata/postgressqlserver\": { \"SingularDisplayName\": \"PostgresSql Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlmanagedinstances\": { \"SingularDisplayName\": \"SQL managed instance - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserveresulicenses\": { \"SingularDisplayName\": \"SQL Server ESU license\" }\n ,\"microsoft.azurearcdata/sqlserverinstances\": { \"SingularDisplayName\": \"SQL Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserverinstances/databases\": { \"SingularDisplayName\": \"SQL Server database - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserverlicenses\": { \"SingularDisplayName\": \"SQL Server License\" }\n ,\"microsoft.azurebusinesscontinuity/deletedunifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity deleted unified protected item\" }\n ,\"microsoft.azurebusinesscontinuity/unifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity unified protected item\" }\n ,\"microsoft.azurecis/aadapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis AAD application\" }\n ,\"microsoft.azurecis/addressrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis address record\" }\n ,\"microsoft.azurecis/autopilotenvironments\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot environment\" }\n ,\"microsoft.azurecis/autopilotmachinefunctions\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot machine function\" }\n ,\"microsoft.azurecis/autopilotsoftwareloadbalancevirtualips\": { \"SingularDisplayName\": \"Microsoft.AzureCis auto pilot software load balance virtual IP\" }\n ,\"microsoft.azurecis/azcopies\": { \"SingularDisplayName\": \"Microsoft.AzureCis az copy\" }\n ,\"microsoft.azurecis/canonicalnamerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis canonical name record\" }\n ,\"microsoft.azurecis/dsmsallowlists\": { \"SingularDisplayName\": \"Microsoft.AzureCis ds msallowlist\" }\n ,\"microsoft.azurecis/dsmscertificates\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms certificate\" }\n ,\"microsoft.azurecis/dsmsrootfolders\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms root folder\" }\n ,\"microsoft.azurecis/dstsapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts application\" }\n ,\"microsoft.azurecis/dstsserviceaccounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service account\" }\n ,\"microsoft.azurecis/dstsserviceclientidentities\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service client identity\" }\n ,\"microsoft.azurecis/genericgenevaactions\": { \"SingularDisplayName\": \"Microsoft.AzureCis generic geneva action\" }\n ,\"microsoft.azurecis/plannedquotas\": { \"SingularDisplayName\": \"Microsoft.AzureCis planned quota\" }\n ,\"microsoft.azurecis/pointerrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis pointer record\" }\n ,\"microsoft.azurecis/publishconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis publish config value\" }\n ,\"microsoft.azurecis/pushagentv2accounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis push agent v2 account\" }\n ,\"microsoft.azurecis/servicerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis service record\" }\n ,\"microsoft.azurecis/sharedconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis shared config value\" }\n ,\"microsoft.azurecloudmetadata/clouds\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata cloud\" }\n ,\"microsoft.azurecloudmetadata/clouds/geographies\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geography\" }\n ,\"microsoft.azurecloudmetadata/clouds/geographies/regions\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geographies region\" }\n ,\"microsoft.azuredatatransfer/connections\": { \"SingularDisplayName\": \"Connection\" }\n ,\"microsoft.azuredatatransfer/connections/flows\": { \"SingularDisplayName\": \"Flow\" }\n ,\"microsoft.azuredatatransfer/pipelines\": { \"SingularDisplayName\": \"Pipeline\" }\n ,\"microsoft.azurefleet/fleets\": { \"SingularDisplayName\": \"Compute Fleet\" }\n ,\"microsoft.azurefleet/fleetscomputehub\": { \"SingularDisplayName\": \"Compute Fleet\" }\n ,\"microsoft.azureimagetestingforlinux/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job\" }\n ,\"microsoft.azureimagetestingforlinux/jobtemplates\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job template\" }\n ,\"microsoft.azurelargeinstance/azurelargeinstances\": { \"SingularDisplayName\": \"Azure Large Instance\" }\n ,\"microsoft.azurelargeinstance/azurelargestorageinstances\": { \"SingularDisplayName\": \"Microsoft.AzureLargeInstance Azure large storage instance\" }\n ,\"microsoft.azurepercept/accounts\": { \"SingularDisplayName\": \"Microsoft.AzurePercept account\" }\n ,\"microsoft.azurepercept/accounts/devices\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts device\" }\n ,\"microsoft.azurepercept/accounts/devices/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts devices sensor\" }\n ,\"microsoft.azurepercept/accounts/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts sensor\" }\n ,\"microsoft.azurepercept/accounts/solutioninstances\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solutioninstance\" }\n ,\"microsoft.azurepercept/accounts/solutions\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solution\" }\n ,\"microsoft.azurepercept/accounts/targets\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts target\" }\n ,\"microsoft.azureplaywrightservice/accounts\": { \"SingularDisplayName\": \"Playwright Testing\" }\n ,\"microsoft.azurescan/scanningaccounts\": { \"SingularDisplayName\": \"ESRP Scan\" }\n ,\"microsoft.azuresphere/catalogs\": { \"SingularDisplayName\": \"Azure Sphere Catalog\" }\n ,\"microsoft.azurespherev2/catalogs\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalog\" }\n ,\"microsoft.azurespherev2/catalogs/artifacts\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs artifact\" }\n ,\"microsoft.azurespherev2/catalogs/certificates\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs certificate\" }\n ,\"microsoft.azurespherev2/catalogs/deviceregistrations\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs device registration\" }\n ,\"microsoft.azurespherev2/catalogs/provisioningpackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs provisioning package\" }\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channel\" }\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels/deployments\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channels deployment\" }\n ,\"microsoft.azurespherev2/catalogs/updatepackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs update package\" }\n ,\"microsoft.azurestack/cloudmanifestfiles\": { \"SingularDisplayName\": \"Microsoft.AzureStack cloud manifest file\" }\n ,\"microsoft.azurestack/linkedsubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack linked subscription\" }\n ,\"microsoft.azurestack/registrations\": { \"SingularDisplayName\": \"Microsoft.AzureStack registration\" }\n ,\"microsoft.azurestack/registrations/customersubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations customer subscription\" }\n ,\"microsoft.azurestack/registrations/products\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations product\" }\n ,\"microsoft.azurestackhci/clusters\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/clusters/updates/updateruns\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/clusters/updatesummaries\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/devicepools\": { \"SingularDisplayName\": \"Azure Stack\" }\n ,\"microsoft.azurestackhci/edgedevices\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge device\" }\n ,\"microsoft.azurestackhci/edgedevices/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge devices job\" }\n ,\"microsoft.azurestackhci/edgemachines\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machine\" }\n ,\"microsoft.azurestackhci/edgemachines/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machines job\" }\n ,\"microsoft.azurestackhci/edgenodepools\": { \"SingularDisplayName\": \"Azure Stack\" }\n ,\"microsoft.azurestackhci/galleryimages\": { \"SingularDisplayName\": \"Azure Local Gallery image\" }\n ,\"microsoft.azurestackhci/logicalnetworks\": { \"SingularDisplayName\": \"Azure Local Logical network\" }\n ,\"microsoft.azurestackhci/marketplacegalleryimages\": { \"SingularDisplayName\": \"Azure Local Marketplace Gallery image\" }\n ,\"microsoft.azurestackhci/networkinterfaces\": { \"SingularDisplayName\": \"Azure Local VM Network Interface\" }\n ,\"microsoft.azurestackhci/networksecuritygroups\": { \"SingularDisplayName\": \"Azure Local Network Security Group\" }\n ,\"microsoft.azurestackhci/networksecuritygroups/securityrules\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI network security groups security rule\" }\n ,\"microsoft.azurestackhci/storagecontainers\": { \"SingularDisplayName\": \"Azure Local Storage path\" }\n ,\"microsoft.azurestackhci/virtualharddisks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual hard disk\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instance\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances guest agent\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.azurestackhci/virtualmachines\": { \"SingularDisplayName\": \"Azure Local virtual machine - Azure Arc\" }\n ,\"microsoft.azurestackhci/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual network\" }\n ,\"microsoft.backupsolutions/vmwareapplications\": { \"SingularDisplayName\": \"Microsoft.BackupSolutions vmware application\" }\n ,\"microsoft.bakeryhybrid/pies\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid py\" }\n ,\"microsoft.bakeryhybrid/pies/nestedresourcetype\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid pies nested resource type\" }\n ,\"microsoft.baremetal/baremetalconnections\": { \"SingularDisplayName\": \"Microsoft.BareMetal bare metal connection\" }\n ,\"microsoft.baremetal/crayservers\": { \"SingularDisplayName\": \"Cray Server\" }\n ,\"microsoft.baremetal/monitoringservers\": { \"SingularDisplayName\": \"Monitoring Server\" }\n ,\"microsoft.baremetal/peeringsettings\": { \"SingularDisplayName\": \"Microsoft.BareMetal peering setting\" }\n ,\"microsoft.baremetalinfrastructure/baremetalinstances\": { \"SingularDisplayName\": \"BareMetal Instance\" }\n ,\"microsoft.baremetalinfrastructure/baremetalstorageinstances\": { \"SingularDisplayName\": \"Microsoft.BareMetalInfrastructure bare metal storage instance\" }\n ,\"microsoft.batch/batchaccounts\": { \"SingularDisplayName\": \"Batch account\" }\n ,\"microsoft.billing/billingaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing account\" }\n ,\"microsoft.billing/billingaccounts/agreements\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts agreement\" }\n ,\"microsoft.billing/billingaccounts/associatedtenants\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts associated tenant\" }\n ,\"microsoft.billing/billingaccounts/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts available balance\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profile\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles available balance\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers transfer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/instructions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles instruction\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice section\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections product\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections transfer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/paymentmethodlinks\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles payment method link\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles policy\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/transactions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles transaction\" }\n ,\"microsoft.billing/billingaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptionaliases\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription aliase\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptions/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscriptions invoice\" }\n ,\"microsoft.billing/billingaccounts/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customer\" }\n ,\"microsoft.billing/billingaccounts/customers/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers billing subscription\" }\n ,\"microsoft.billing/billingaccounts/customers/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers policy\" }\n ,\"microsoft.billing/billingaccounts/customers/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers product\" }\n ,\"microsoft.billing/billingaccounts/departments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts department\" }\n ,\"microsoft.billing/billingaccounts/departments/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/departments/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role definition\" }\n ,\"microsoft.billing/billingaccounts/departments/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments enrollment account\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment account\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role definition\" }\n ,\"microsoft.billing/billingaccounts/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\n ,\"microsoft.billing/billingaccounts/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\n ,\"microsoft.billing/billingaccounts/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice\" }\n ,\"microsoft.billing/billingaccounts/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice section\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections billing subscription\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections product\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections transfer\" }\n ,\"microsoft.billing/billingaccounts/lineofcredit\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts line of credit\" }\n ,\"microsoft.billing/billingaccounts/migrations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts migration\" }\n ,\"microsoft.billing/billingaccounts/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts payment method\" }\n ,\"microsoft.billing/billingaccounts/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts policy\" }\n ,\"microsoft.billing/billingaccounts/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts product\" }\n ,\"microsoft.billing/billingaccounts/reservationorders\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation order\" }\n ,\"microsoft.billing/billingaccounts/reservationorders/reservations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation orders reservation\" }\n ,\"microsoft.billing/billingaccounts/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\n ,\"microsoft.billing/billingaccounts/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\n ,\"microsoft.billing/billingperiods\": { \"SingularDisplayName\": \"Microsoft.Billing billing period\" }\n ,\"microsoft.billing/billingproperty\": { \"SingularDisplayName\": \"Microsoft.Billing billing property\" }\n ,\"microsoft.billing/billingrequests\": { \"SingularDisplayName\": \"Microsoft.Billing billing request\" }\n ,\"microsoft.billing/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing role assignment\" }\n ,\"microsoft.billing/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing role definition\" }\n ,\"microsoft.billing/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing enrollment account\" }\n ,\"microsoft.billing/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing payment method\" }\n ,\"microsoft.billing/policies\": { \"SingularDisplayName\": \"Microsoft.Billing policy\" }\n ,\"microsoft.billing/promotions\": { \"SingularDisplayName\": \"Microsoft.Billing promotion\" }\n ,\"microsoft.billing/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing transfer\" }\n ,\"microsoft.billingbenefits/credits\": { \"SingularDisplayName\": \"Credit\" }\n ,\"microsoft.billingbenefits/discounts\": { \"SingularDisplayName\": \"Discount\" }\n ,\"microsoft.billingbenefits/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\n ,\"microsoft.billingbenefits/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\n ,\"microsoft.billingbenefits/maccs\": { \"SingularDisplayName\": \"Microsoft Azure Consumption Commitment\" }\n ,\"microsoft.billingbenefits/reservationorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits reservation order aliase\" }\n ,\"microsoft.billingbenefits/savingsplanorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits savings plan order aliase\" }\n ,\"microsoft.billingbenefits/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\n ,\"microsoft.billingbenefits/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\n ,\"microsoft.bing/accounts\": { \"SingularDisplayName\": \"Bing Resource\" }\n ,\"microsoft.blockchain/blockchainmembers\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain member\" }\n ,\"microsoft.blockchain/blockchainmembers/transactionnodes\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain members transaction node\" }\n ,\"microsoft.blockchaintokens/tokenservices\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token service\" }\n ,\"microsoft.blockchaintokens/tokenservices/blockchainnetworks\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services blockchain network\" }\n ,\"microsoft.blockchaintokens/tokenservices/groups\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services group\" }\n ,\"microsoft.blockchaintokens/tokenservices/groups/accounts\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services groups account\" }\n ,\"microsoft.blockchaintokens/tokenservices/tokentemplates\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services token template\" }\n ,\"microsoft.bluefin/instances\": { \"SingularDisplayName\": \"Microsoft.Bluefin instance\" }\n ,\"microsoft.bluefin/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances dataset\" }\n ,\"microsoft.bluefin/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances pipeline\" }\n ,\"microsoft.blueprint/blueprintassignments\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint assignment\" }\n ,\"microsoft.blueprint/blueprints\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint\" }\n ,\"microsoft.blueprint/blueprints/artifacts\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints artifact\" }\n ,\"microsoft.blueprint/blueprints/versions\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints version\" }\n ,\"microsoft.botservice/botservices\": { \"SingularDisplayName\": \"Bot Service\" }\n ,\"microsoft.cache/redis\": { \"SingularDisplayName\": \"Redis cache\" }\n ,\"microsoft.cache/redisenterprise\": { \"SingularDisplayName\": \"Azure Managed Redis\" }\n ,\"microsoft.cache/redisenterprise/databases\": { \"SingularDisplayName\": \"Redis Enterprise database\" }\n ,\"microsoft.capacity/reservationorders\": { \"SingularDisplayName\": \"Reservation order\" }\n ,\"microsoft.capacity/reservationorders/reservations\": { \"SingularDisplayName\": \"Reservation\" }\n ,\"microsoft.cascade/sites\": { \"SingularDisplayName\": \"Microsoft.Cascade site\" }\n ,\"microsoft.cdn/cdnwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Content Delivery Network WAF policy\" }\n ,\"microsoft.cdn/edgeactions\": { \"SingularDisplayName\": \"Edge Action\" }\n ,\"microsoft.cdn/profiles\": { \"SingularDisplayName\": \"Front Door and CDN profile\" }\n ,\"microsoft.cdn/profiles/afdendpoints\": { \"SingularDisplayName\": \"Endpoint\" }\n ,\"microsoft.cdn/profiles/afdendpoints/routes\": { \"SingularDisplayName\": \"Route\" }\n ,\"microsoft.cdn/profiles/customdomains\": { \"SingularDisplayName\": \"Custom domain\" }\n ,\"microsoft.cdn/profiles/endpoints\": { \"SingularDisplayName\": \"CDN endpoint\" }\n ,\"microsoft.cdn/profiles/endpoints/customdomains\": { \"SingularDisplayName\": \"CDN custom domain\" }\n ,\"microsoft.cdn/profiles/endpoints/origins\": { \"SingularDisplayName\": \"CDN origin\" }\n ,\"microsoft.cdn/profiles/origingroups\": { \"SingularDisplayName\": \"Origin group\" }\n ,\"microsoft.cdn/profiles/origingroups/origins\": { \"SingularDisplayName\": \"Origin\" }\n ,\"microsoft.cdn/profiles/rulesets\": { \"SingularDisplayName\": \"Rule set\" }\n ,\"microsoft.cdn/profiles/rulesets/rules\": { \"SingularDisplayName\": \"Rule\" }\n ,\"microsoft.cdn/profiles/secrets\": { \"SingularDisplayName\": \"Secret\" }\n ,\"microsoft.cdn/profiles/securitypolicies\": { \"SingularDisplayName\": \"Security policy\" }\n ,\"microsoft.certificateregistration/certificateorders\": { \"SingularDisplayName\": \"App Service certificate\" }\n ,\"microsoft.certify/testsuites\": { \"SingularDisplayName\": \"Microsoft.Certify test suite\" }\n ,\"microsoft.certify/validationjobs\": { \"SingularDisplayName\": \"Microsoft.Certify validation job\" }\n ,\"microsoft.changeanalysis/profile\": { \"SingularDisplayName\": \"Microsoft.ChangeAnalysis profile\" }\n ,\"microsoft.changesafety/changestates\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change state\" }\n ,\"microsoft.changesafety/changestates/stageprogressions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change states stage progression\" }\n ,\"microsoft.changesafety/stagemaps\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety stage map\" }\n ,\"microsoft.changesafety/validations\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validation\" }\n ,\"microsoft.changesafety/validators\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validator\" }\n ,\"microsoft.changesafety/validators/versions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validators version\" }\n ,\"microsoft.chaos/experiments\": { \"SingularDisplayName\": \"Chaos Experiment\" }\n ,\"microsoft.chaos/privateaccesses\": { \"SingularDisplayName\": \"Agent Private Access\" }\n ,\"microsoft.chaos/targets\": { \"SingularDisplayName\": \"Microsoft.Chaos target\" }\n ,\"microsoft.chaos/targets/capabilities\": { \"SingularDisplayName\": \"Microsoft.Chaos targets capability\" }\n ,\"microsoft.classiccompute/domainnames\": { \"SingularDisplayName\": \"Cloud service (classic)\" }\n ,\"microsoft.classiccompute/domainnames/slots/roles\": { \"SingularDisplayName\": \"Cloud service role (classic)\" }\n ,\"microsoft.classiccompute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine (classic)\" }\n ,\"microsoft.classicnetwork/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group (classic)\" }\n ,\"microsoft.classicnetwork/reservedips\": { \"SingularDisplayName\": \"Reserved IP address (classic)\" }\n ,\"microsoft.classicnetwork/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network (classic)\" }\n })[tolower(id)]\n}\n", - "$fxv#1": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_2(id: string) {\n dynamic({\n \"microsoft.classicstorage/storageaccounts\": { \"SingularDisplayName\": \"Storage account (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/disks\": { \"SingularDisplayName\": \"Disk (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/osimages\": { \"SingularDisplayName\": \"OS image (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/vmimages\": { \"SingularDisplayName\": \"VM image (classic)\" }\n ,\"microsoft.cleanroom/cleanrooms\": { \"SingularDisplayName\": \"Microsoft.CleanRoom cleanroom\" }\n ,\"microsoft.cleanroom/collaborations\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaboration\" }\n ,\"microsoft.cleanroom/collaborations/contracts\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaborations contract\" }\n ,\"microsoft.cleanroom/consortiums\": { \"SingularDisplayName\": \"Microsoft.CleanRoom consortium\" }\n ,\"microsoft.cleanroom/microservices\": { \"SingularDisplayName\": \"Microsoft.CleanRoom microservice\" }\n ,\"microsoft.cloud/hubs\": { \"SingularDisplayName\": \"FinOps hub\" }\n ,\"microsoft.clouddeviceplatform/delegatedidentities\": { \"SingularDisplayName\": \"Microsoft.CloudDevicePlatform delegated identity\" }\n ,\"microsoft.cloudhealth/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\n ,\"microsoft.cloudtest/accounts\": { \"SingularDisplayName\": \"CloudTest Account\" }\n ,\"microsoft.cloudtest/buildcaches\": { \"SingularDisplayName\": \"1ES Build Cache\" }\n ,\"microsoft.cloudtest/hostedpools\": { \"SingularDisplayName\": \"1ES Hosted Pool\" }\n ,\"microsoft.cloudtest/images\": { \"SingularDisplayName\": \"1ES Image\" }\n ,\"microsoft.cloudtest/pools\": { \"SingularDisplayName\": \"CloudTest Pool\" }\n ,\"microsoft.clusterstor/nodes\": { \"SingularDisplayName\": \"ClusterStor\" }\n ,\"microsoft.codesigning/codesigningaccounts\": { \"SingularDisplayName\": \"Trusted Signing Account\" }\n ,\"microsoft.codespaces/plans\": { \"SingularDisplayName\": \"Microsoft.Codespaces plan\" }\n ,\"microsoft.cognitiveservices/accounts\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.cognitiveservices/accounts/projects\": { \"SingularDisplayName\": \"Azure AI Foundry project\" }\n ,\"microsoft.cognitiveservices/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plan\" }\n ,\"microsoft.cognitiveservices/commitmentplans/accountassociations\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plans account association\" }\n ,\"microsoft.communication/communicationservices\": { \"SingularDisplayName\": \"Communication Service\" }\n ,\"microsoft.communication/emailservices\": { \"SingularDisplayName\": \"Email Communication Service\" }\n ,\"microsoft.communication/emailservices/domains\": { \"SingularDisplayName\": \"Email Communication Services Domain\" }\n ,\"microsoft.community/communitytrainings\": { \"SingularDisplayName\": \"Community Training\" }\n ,\"microsoft.compositesolutions/compositesolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution definition\" }\n ,\"microsoft.compositesolutions/compositesolutions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution\" }\n ,\"microsoft.compute/availabilitysets\": { \"SingularDisplayName\": \"Availability set\" }\n ,\"microsoft.compute/capacityreservationgroups\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\n ,\"microsoft.compute/capacityreservationgroups/capacityreservations\": { \"SingularDisplayName\": \"Capacity reservation\" }\n ,\"microsoft.compute/capacityreservationgroupscomputehub\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\n ,\"microsoft.compute/cloudservices\": { \"SingularDisplayName\": \"Cloud service (extended support)\" }\n ,\"microsoft.compute/computefleetinstances\": { \"SingularDisplayName\": \"Instance\" }\n ,\"microsoft.compute/computefleetscalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.compute/diskaccesses\": { \"SingularDisplayName\": \"Disk Access\" }\n ,\"microsoft.compute/diskencryptionsets\": { \"SingularDisplayName\": \"Disk Encryption Set\" }\n ,\"microsoft.compute/disks\": { \"SingularDisplayName\": \"Disk\" }\n ,\"microsoft.compute/galleries\": { \"SingularDisplayName\": \"Azure compute gallery\" }\n ,\"microsoft.compute/galleries/applications\": { \"SingularDisplayName\": \"VM application definition\" }\n ,\"microsoft.compute/galleries/applications/versions\": { \"SingularDisplayName\": \"VM application version\" }\n ,\"microsoft.compute/galleries/images\": { \"SingularDisplayName\": \"VM image definition\" }\n ,\"microsoft.compute/galleries/images/versions\": { \"SingularDisplayName\": \"VM image version\" }\n ,\"microsoft.compute/galleries/imagescomputehub\": { \"SingularDisplayName\": \"VM image definition\" }\n ,\"microsoft.compute/hostgroups\": { \"SingularDisplayName\": \"Host group\" }\n ,\"microsoft.compute/hostgroups/hosts\": { \"SingularDisplayName\": \"Host\" }\n ,\"microsoft.compute/hostgroupscomputehub\": { \"SingularDisplayName\": \"Host group\" }\n ,\"microsoft.compute/images\": { \"SingularDisplayName\": \"Image\" }\n ,\"microsoft.compute/imagescomputehub\": { \"SingularDisplayName\": \"Image\" }\n ,\"microsoft.compute/locations/communitygalleries/images\": { \"SingularDisplayName\": \"Community image\" }\n ,\"microsoft.compute/locations/communitygalleries/imagescomputehub\": { \"SingularDisplayName\": \"Community image\" }\n ,\"microsoft.compute/proximityplacementgroups\": { \"SingularDisplayName\": \"Proximity placement group\" }\n ,\"microsoft.compute/proximityplacementgroupscomputehub\": { \"SingularDisplayName\": \"Proximity placement group\" }\n ,\"microsoft.compute/restorepointcollections\": { \"SingularDisplayName\": \"Restore Point Collection\" }\n ,\"microsoft.compute/restorepointcollections/restorepoints\": { \"SingularDisplayName\": \"Restore Point\" }\n ,\"microsoft.compute/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\n ,\"microsoft.compute/sshpublickeys\": { \"SingularDisplayName\": \"SSH key\" }\n ,\"microsoft.compute/standbypoolinstance\": { \"SingularDisplayName\": \"Standby pool\" }\n ,\"microsoft.compute/virtualmachinecomputehub\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.compute/virtualmachineflexinstances\": { \"SingularDisplayName\": \"Instance\" }\n ,\"microsoft.compute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.compute/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.compute/virtualmachinescalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.compute/virtualmachinescalesets/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine scale set instance\" }\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines/networkinterfaces/ipconfigurations/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\n ,\"microsoft.compute/virtualmachinescalesetscomputehub\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.computehub/advisorcost\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisoroperationalexcellence\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorperformance\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorreliability\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorsecurity\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/all\": { \"SingularDisplayName\": \"All resources\" }\n ,\"microsoft.computehub/backup\": { \"SingularDisplayName\": \"Backup job\" }\n ,\"microsoft.computehub/computehubmain\": { \"SingularDisplayName\": \"Compute infrastructure\" }\n ,\"microsoft.computehub/healthevents\": { \"SingularDisplayName\": \"Health events\" }\n ,\"microsoft.computehub/linuxostype\": { \"SingularDisplayName\": \"Linux OS\" }\n ,\"microsoft.computehub/microsoftdefenderfreetrialsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\n ,\"microsoft.computehub/microsoftdefenderstandardsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\n ,\"microsoft.computehub/outages\": { \"SingularDisplayName\": \"Outages\" }\n ,\"microsoft.computehub/powerstatedeallocated\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/powerstaterunning\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/powerstatestopped\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/provisioningstatefailedresources\": { \"SingularDisplayName\": \"Provisioning states\" }\n ,\"microsoft.computehub/provisioningstatesucceededresources\": { \"SingularDisplayName\": \"Provisioning states\" }\n ,\"microsoft.computehub/windowsostype\": { \"SingularDisplayName\": \"Windows OS\" }\n ,\"microsoft.computeschedule/autoactions\": { \"SingularDisplayName\": \"Automatic Action\" }\n ,\"microsoft.computeschedule/autoactions/occurrences\": { \"SingularDisplayName\": \"Microsoft.ComputeSchedule auto actions occurrence\" }\n ,\"microsoft.confidentialledger/ledgers\": { \"SingularDisplayName\": \"Confidential Ledger\" }\n ,\"microsoft.confidentialledger/managedccfs\": { \"SingularDisplayName\": \"Managed CCF App\" }\n ,\"microsoft.confluent/agreements\": { \"SingularDisplayName\": \"Microsoft.Confluent agreement\" }\n ,\"microsoft.confluent/organizations\": { \"SingularDisplayName\": \"Confluent organization\" }\n ,\"microsoft.connectedcache/cachenodes\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\n ,\"microsoft.connectedcache/enterprisecustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\n ,\"microsoft.connectedcache/enterprisemcccustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\n ,\"microsoft.connectedcache/enterprisemcccustomers/enterprisemcccachenodes\": { \"SingularDisplayName\": \"MCC CacheNode for Enterprise\" }\n ,\"microsoft.connectedcache/ispcustomers\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\n ,\"microsoft.connectedcredentials/credentials\": { \"SingularDisplayName\": \"Microsoft.ConnectedCredentials credential\" }\n ,\"microsoft.connectedvehicle/platformaccounts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVehicle platform account\" }\n ,\"microsoft.connectedvmwarevsphere/clusters\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere cluster\" }\n ,\"microsoft.connectedvmwarevsphere/datastores\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere datastore\" }\n ,\"microsoft.connectedvmwarevsphere/hosts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere host\" }\n ,\"microsoft.connectedvmwarevsphere/resourcepools\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere resource pool\" }\n ,\"microsoft.connectedvmwarevsphere/vcenters\": { \"SingularDisplayName\": \"VMware vCenter\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instance\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances guest agent\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachines\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine template\" }\n ,\"microsoft.connectedvmwarevsphere/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual network\" }\n ,\"microsoft.consumption/budgets\": { \"SingularDisplayName\": \"Microsoft.Consumption budget\" }\n ,\"microsoft.consumption/credits\": { \"SingularDisplayName\": \"Microsoft.Consumption credit\" }\n ,\"microsoft.consumption/pricesheets\": { \"SingularDisplayName\": \"Microsoft.Consumption pricesheet\" }\n ,\"microsoft.containerinstance/containergroupprofiles\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profile\" }\n ,\"microsoft.containerinstance/containergroupprofiles/revisions\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profiles revision\" }\n ,\"microsoft.containerinstance/containergroups\": { \"SingularDisplayName\": \"Container instances\" }\n ,\"microsoft.containerinstance/ngroups\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance ngroup\" }\n ,\"microsoft.containerregistry/registries\": { \"SingularDisplayName\": \"Container registry\" }\n ,\"microsoft.containerregistry/registries/replications\": { \"SingularDisplayName\": \"Container registry replication\" }\n ,\"microsoft.containerregistry/registries/scopemaps\": { \"SingularDisplayName\": \"Container registry scope map\" }\n ,\"microsoft.containerregistry/registries/tokens\": { \"SingularDisplayName\": \"Container registry token\" }\n ,\"microsoft.containerregistry/registries/webhooks\": { \"SingularDisplayName\": \"Container registry webhook\" }\n ,\"microsoft.containerservice/fleets\": { \"SingularDisplayName\": \"Kubernetes fleet manager\" }\n ,\"microsoft.containerservice/managedclusters\": { \"SingularDisplayName\": \"Kubernetes service\" }\n ,\"microsoft.containerservice/managedclusters/managednamespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes namespace\" }\n ,\"microsoft.containerservice/managedclusters/namespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\n ,\"microsoft.containerservice/managedclustersnapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService managedclustersnapshot\" }\n ,\"microsoft.containerservice/snapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService snapshot\" }\n ,\"microsoft.containerstorage/pools\": { \"SingularDisplayName\": \"Container storage\" }\n ,\"microsoft.costmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.CostManagement alert\" }\n ,\"microsoft.costmanagement/budgets\": { \"SingularDisplayName\": \"Microsoft.CostManagement budget\" }\n ,\"microsoft.costmanagement/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement cloud connector\" }\n ,\"microsoft.costmanagement/connectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement connector\" }\n ,\"microsoft.costmanagement/costallocationrules\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost allocation rule\" }\n ,\"microsoft.costmanagement/costdetailsoperationresults\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost details operation result\" }\n ,\"microsoft.costmanagement/exports\": { \"SingularDisplayName\": \"Microsoft.CostManagement export\" }\n ,\"microsoft.costmanagement/externalbillingaccounts\": { \"SingularDisplayName\": \"Microsoft.CostManagement external billing account\" }\n ,\"microsoft.costmanagement/externalsubscriptions\": { \"SingularDisplayName\": \"Microsoft.CostManagement external subscription\" }\n ,\"microsoft.costmanagement/markuprules\": { \"SingularDisplayName\": \"Microsoft.CostManagement markup rule\" }\n ,\"microsoft.costmanagement/operationstatus\": { \"SingularDisplayName\": \"Microsoft.CostManagement operation statu\" }\n ,\"microsoft.costmanagement/reportconfigs\": { \"SingularDisplayName\": \"Microsoft.CostManagement reportconfig\" }\n ,\"microsoft.costmanagement/reports\": { \"SingularDisplayName\": \"Microsoft.CostManagement report\" }\n ,\"microsoft.costmanagement/scheduledactions\": { \"SingularDisplayName\": \"Microsoft.CostManagement scheduled action\" }\n ,\"microsoft.costmanagement/settings\": { \"SingularDisplayName\": \"Microsoft.CostManagement setting\" }\n ,\"microsoft.costmanagement/views\": { \"SingularDisplayName\": \"Microsoft.CostManagement view\" }\n ,\"microsoft.customerlockbox/requests\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox request\" }\n ,\"microsoft.customerlockbox/tenantoptedin\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox tenant opted in\" }\n ,\"microsoft.customproviders/associations\": { \"SingularDisplayName\": \"Microsoft.CustomProviders association\" }\n ,\"microsoft.customproviders/resourceproviders\": { \"SingularDisplayName\": \"Microsoft.CustomProviders resource provider\" }\n ,\"microsoft.dashboard/dashboards\": { \"SingularDisplayName\": \"Azure Monitor dashboards with Grafana\" }\n ,\"microsoft.dashboard/grafana\": { \"SingularDisplayName\": \"Azure Managed Grafana\" }\n ,\"microsoft.dataaccelerator/indexclusters\": { \"SingularDisplayName\": \"Microsoft.DataAccelerator index cluster\" }\n ,\"microsoft.databasefleetmanager/fleets\": { \"SingularDisplayName\": \"Database fleet manager\" }\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces\": { \"SingularDisplayName\": \"Fleetspaces\" }\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces/databases\": { \"SingularDisplayName\": \"Fleet managed database\" }\n ,\"microsoft.databasefleetmanager/fleets/tiers\": { \"SingularDisplayName\": \"tier\" }\n ,\"microsoft.databasewatcher/watchers\": { \"SingularDisplayName\": \"Database watcher\" }\n ,\"microsoft.databox/jobs\": { \"SingularDisplayName\": \"Azure Data Box\" }\n ,\"microsoft.databoxedge/databoxedgedevices\": { \"SingularDisplayName\": \"Azure Stack Edge / Data Box Gateway\" }\n ,\"microsoft.databricks/accessconnectors\": { \"SingularDisplayName\": \"Access Connector for Azure Databricks\" }\n ,\"microsoft.databricks/workspaces\": { \"SingularDisplayName\": \"Azure Databricks Service\" }\n ,\"microsoft.datacatalog/catalogs\": { \"SingularDisplayName\": \"Data catalog\" }\n ,\"microsoft.datacollaboration/workspaces\": { \"SingularDisplayName\": \"Project CI\" }\n ,\"microsoft.datadog/agreements\": { \"SingularDisplayName\": \"Microsoft.Datadog agreement\" }\n ,\"microsoft.datadog/monitors\": { \"SingularDisplayName\": \"Datadog\" }\n ,\"microsoft.datadog/subscriptionstatuses\": { \"SingularDisplayName\": \"Microsoft.Datadog subscription statuse\" }\n ,\"microsoft.datafactory/datafactories\": { \"SingularDisplayName\": \"Data factory\" }\n ,\"microsoft.datafactory/factories\": { \"SingularDisplayName\": \"Data factory (V2)\" }\n ,\"microsoft.datafactory/factories/pipelines\": { \"SingularDisplayName\": \"Data Factory pipeline\" }\n ,\"microsoft.datafactory/factories/triggers\": { \"SingularDisplayName\": \"Data Factory trigger\" }\n ,\"microsoft.datalakeanalytics/accounts\": { \"SingularDisplayName\": \"Data Lake Analytics account\" }\n ,\"microsoft.datalakestore/accounts\": { \"SingularDisplayName\": \"Data Lake Storage Gen1\" }\n ,\"microsoft.datamigration/databasemigrations\": { \"SingularDisplayName\": \"Microsoft.DataMigration database migration\" }\n ,\"microsoft.datamigration/migrationservices\": { \"SingularDisplayName\": \"Microsoft.DataMigration migration service\" }\n ,\"microsoft.datamigration/services\": { \"SingularDisplayName\": \"Azure Database Migration Service (classic)\" }\n ,\"microsoft.datamigration/services/projects\": { \"SingularDisplayName\": \"Azure Database Migration Project\" }\n ,\"microsoft.datamigration/sqlmigrationservices\": { \"SingularDisplayName\": \"Azure Database Migration Service\" }\n ,\"microsoft.dataprotection/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\n ,\"microsoft.dataprotection/resourceguards\": { \"SingularDisplayName\": \"Resource Guard\" }\n ,\"microsoft.datareplication/replicationfabrics\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabric\" }\n ,\"microsoft.datareplication/replicationfabrics/fabricagents\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agent\" }\n ,\"microsoft.datareplication/replicationfabrics/fabricagents/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agents operation\" }\n ,\"microsoft.datareplication/replicationfabrics/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics operation\" }\n ,\"microsoft.datareplication/replicationvaults\": { \"SingularDisplayName\": \"Data replication vault\" }\n ,\"microsoft.datareplication/replicationvaults/alertsettings\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults alert setting\" }\n ,\"microsoft.datareplication/replicationvaults/events\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults event\" }\n ,\"microsoft.datareplication/replicationvaults/jobs\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults job\" }\n ,\"microsoft.datareplication/replicationvaults/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults jobs operation\" }\n ,\"microsoft.datareplication/replicationvaults/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults operation\" }\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnectionproxies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection proxy\" }\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection\" }\n ,\"microsoft.datareplication/replicationvaults/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private link resource\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected item\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items operation\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems/recoverypoints\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items recovery point\" }\n ,\"microsoft.datareplication/replicationvaults/replicationextensions\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extension\" }\n ,\"microsoft.datareplication/replicationvaults/replicationextensions/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extensions operation\" }\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policy\" }\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policies operation\" }\n ,\"microsoft.datashare/accounts\": { \"SingularDisplayName\": \"Data Share\" }\n ,\"microsoft.dbformariadb/servers\": { \"SingularDisplayName\": \"Azure Database for MariaDB server\" }\n ,\"microsoft.dbformysql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for MySQL flexible server\" }\n ,\"microsoft.dbformysql/servers\": { \"SingularDisplayName\": \"MySQL server\" }\n ,\"microsoft.dbforpostgresql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for PostgreSQL flexible server\" }\n ,\"microsoft.dbforpostgresql/servergroupsv2\": { \"SingularDisplayName\": \"Azure Cosmos DB for PostgreSQL Cluster\" }\n ,\"microsoft.dbforpostgresql/servers\": { \"SingularDisplayName\": \"PostgreSQL server\" }\n ,\"microsoft.delegatednetwork/controller\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork controller\" }\n ,\"microsoft.delegatednetwork/delegatedsubnets\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork delegated subnet\" }\n ,\"microsoft.delegatednetwork/orchestrators\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork orchestrator\" }\n ,\"microsoft.dependencymap/maps\": { \"SingularDisplayName\": \"Microsoft.DependencyMap map\" }\n ,\"microsoft.dependencymap/maps/discoverysources\": { \"SingularDisplayName\": \"Microsoft.DependencyMap maps discovery source\" }\n ,\"microsoft.deploymentmanager/artifactsources\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager artifact source\" }\n ,\"microsoft.deploymentmanager/rollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.deploymentmanager/servicetopologies\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topology\" }\n ,\"microsoft.deploymentmanager/servicetopologies/services\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies service\" }\n ,\"microsoft.deploymentmanager/servicetopologies/services/serviceunits\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies services service unit\" }\n ,\"microsoft.deploymentmanager/steps\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager step\" }\n ,\"microsoft.desktopvirtualization/appattachpackages\": { \"SingularDisplayName\": \"App attach package\" }\n ,\"microsoft.desktopvirtualization/applicationgroups\": { \"SingularDisplayName\": \"Application group\" }\n ,\"microsoft.desktopvirtualization/hostpools\": { \"SingularDisplayName\": \"Host pool\" }\n ,\"microsoft.desktopvirtualization/scalingplans\": { \"SingularDisplayName\": \"Scaling plan\" }\n ,\"microsoft.desktopvirtualization/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.devai/instances\": { \"SingularDisplayName\": \"Microsoft.DevAI instance\" }\n ,\"microsoft.devai/instances/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances experiment\" }\n ,\"microsoft.devai/instances/sandboxes\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandbox\" }\n ,\"microsoft.devai/instances/sandboxes/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandboxes experiment\" }\n ,\"microsoft.devcenter/devcenters\": { \"SingularDisplayName\": \"Dev center\" }\n ,\"microsoft.devcenter/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Dev Box definition\" }\n ,\"microsoft.devcenter/networkconnections\": { \"SingularDisplayName\": \"Network connection\" }\n ,\"microsoft.devcenter/plans\": { \"SingularDisplayName\": \"Dev center plan\" }\n ,\"microsoft.devcenter/projects\": { \"SingularDisplayName\": \"Project\" }\n ,\"microsoft.devcenter/projects/pools\": { \"SingularDisplayName\": \"Pool\" }\n ,\"microsoft.developmentwindows365/developmentcloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.DevelopmentWindows365 development cloud pc delegated msi\" }\n ,\"microsoft.devhub/iacprofiles\": { \"SingularDisplayName\": \"Infrastructure as Code Automation\" }\n ,\"microsoft.devhub/templates\": { \"SingularDisplayName\": \"Microsoft.DevHub template\" }\n ,\"microsoft.devhub/templates/versions\": { \"SingularDisplayName\": \"Microsoft.DevHub templates version\" }\n ,\"microsoft.devhub/workflows\": { \"SingularDisplayName\": \"Microsoft.DevHub workflow\" }\n ,\"microsoft.deviceonboarding/discoveryservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery service\" }\n ,\"microsoft.deviceonboarding/discoveryservices/ownershipvoucherpublickeys\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery services ownership voucher public key\" }\n ,\"microsoft.deviceonboarding/onboardingservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding service\" }\n ,\"microsoft.deviceonboarding/onboardingservices/policies\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding services policy\" }\n ,\"microsoft.deviceregistry/assetendpointprofiles\": { \"SingularDisplayName\": \"IoT Asset Endpoint Profile\" }\n ,\"microsoft.deviceregistry/assets\": { \"SingularDisplayName\": \"IoT Asset\" }\n ,\"microsoft.deviceregistry/billingcontainers\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry billing container\" }\n ,\"microsoft.deviceregistry/devices\": { \"SingularDisplayName\": \"IoT Device\" }\n ,\"microsoft.deviceregistry/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset endpoint profile\" }\n ,\"microsoft.deviceregistry/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset\" }\n ,\"microsoft.deviceregistry/namespaces\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespace\" }\n ,\"microsoft.deviceregistry/namespaces/assetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset endpoint profile\" }\n ,\"microsoft.deviceregistry/namespaces/assets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset\" }\n ,\"microsoft.deviceregistry/namespaces/devices\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces device\" }\n ,\"microsoft.deviceregistry/namespaces/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset endpoint profile\" }\n ,\"microsoft.deviceregistry/namespaces/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset\" }\n ,\"microsoft.deviceregistry/schemaregistries\": { \"SingularDisplayName\": \"IoT Schema Registry\" }\n ,\"microsoft.deviceregistry/schemaregistries/schemas\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schema\" }\n ,\"microsoft.deviceregistry/schemaregistries/schemas/schemaversions\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schemas schema version\" }\n ,\"microsoft.devices/iothubs\": { \"SingularDisplayName\": \"IoT hub\" }\n ,\"microsoft.devices/provisioningservices\": { \"SingularDisplayName\": \"Azure IoT Hub Device Provisioning Service (DPS)\" }\n ,\"microsoft.deviceupdate/accounts\": { \"SingularDisplayName\": \"Device Update for IoT Hub\" }\n ,\"microsoft.deviceupdate/updateaccounts\": { \"SingularDisplayName\": \"Device Update Account\" }\n ,\"microsoft.deviceupdate/updateaccounts/activedeployments\": { \"SingularDisplayName\": \"Device Update Active Deployment\" }\n ,\"microsoft.deviceupdate/updateaccounts/agents\": { \"SingularDisplayName\": \"Device Update Agent\" }\n ,\"microsoft.deviceupdate/updateaccounts/deployments\": { \"SingularDisplayName\": \"Device Update Deployment\" }\n ,\"microsoft.deviceupdate/updateaccounts/deviceclasses\": { \"SingularDisplayName\": \"Device Update Device Class\" }\n ,\"microsoft.deviceupdate/updateaccounts/updates\": { \"SingularDisplayName\": \"Device Update\" }\n ,\"microsoft.devops/pipelines\": { \"SingularDisplayName\": \"Microsoft.DevOps pipeline\" }\n ,\"microsoft.devopsinfrastructure/pools\": { \"SingularDisplayName\": \"Managed DevOps Pool\" }\n ,\"microsoft.devspaces/controllers\": { \"SingularDisplayName\": \"Microsoft.DevSpaces controller\" }\n ,\"microsoft.devtestlab/labs\": { \"SingularDisplayName\": \"DevTest lab\" }\n ,\"microsoft.devtestlab/labs/virtualmachines\": { \"SingularDisplayName\": \"DevTest Lab virtual machine\" }\n ,\"microsoft.devtestlab/schedules\": { \"SingularDisplayName\": \"Microsoft.DevTestLab schedule\" }\n ,\"microsoft.devtunnels/tunnelplans\": { \"SingularDisplayName\": \"Dev Tunnels Domain\" }\n ,\"microsoft.diagnostics/apollo\": { \"SingularDisplayName\": \"Microsoft.Diagnostics apollo\" }\n ,\"microsoft.digitaltwins/digitaltwinsinstances\": { \"SingularDisplayName\": \"Azure Digital Twins\" }\n ,\"microsoft.discovery/agents\": { \"SingularDisplayName\": \"Microsoft Discovery Agent\" }\n ,\"microsoft.discovery/bookshelves\": { \"SingularDisplayName\": \"Microsoft Discovery Bookshelf\" }\n ,\"microsoft.discovery/datacontainers\": { \"SingularDisplayName\": \"Microsoft Discovery Data Container\" }\n ,\"microsoft.discovery/datacontainers/dataassets\": { \"SingularDisplayName\": \"Data asset\" }\n ,\"microsoft.discovery/models\": { \"SingularDisplayName\": \"Microsoft Discovery Model\" }\n ,\"microsoft.discovery/storages\": { \"SingularDisplayName\": \"Microsoft Discovery Storage\" }\n ,\"microsoft.discovery/supercomputers\": { \"SingularDisplayName\": \"Microsoft Discovery Supercomputer\" }\n ,\"microsoft.discovery/supercomputers/nodepools\": { \"SingularDisplayName\": \"Nodepool\" }\n ,\"microsoft.discovery/tools\": { \"SingularDisplayName\": \"Microsoft Discovery Tool\" }\n ,\"microsoft.discovery/workflows\": { \"SingularDisplayName\": \"Microsoft Discovery Workflow\" }\n ,\"microsoft.discovery/workspaces\": { \"SingularDisplayName\": \"Microsoft Discovery Workspace\" }\n ,\"microsoft.discovery/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft Discovery Project\" }\n ,\"microsoft.documentdb/cassandraclusters\": { \"SingularDisplayName\": \"Azure Managed Instance for Apache Cassandra\" }\n ,\"microsoft.documentdb/databaseaccounts\": { \"SingularDisplayName\": \"Cosmos DB account\" }\n ,\"microsoft.documentdb/fleets\": { \"SingularDisplayName\": \"Azure Cosmos DB Fleet\" }\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccounts\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccountswithlocations\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\n ,\"microsoft.documentdb/mongoclusters\": { \"SingularDisplayName\": \"Azure Cosmos DB for MongoDB (vCore)\" }\n ,\"microsoft.documentdb/throughputpools\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pool\" }\n ,\"microsoft.documentdb/throughputpools/throughputpoolaccounts\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pools throughput pool account\" }\n ,\"microsoft.domainregistration/domains\": { \"SingularDisplayName\": \"App Service Domain\" }\n ,\"microsoft.domainregistration/topleveldomains\": { \"SingularDisplayName\": \"Microsoft.DomainRegistration top level domain\" }\n ,\"microsoft.durabletask/namespaces\": { \"SingularDisplayName\": \"Microsoft.DurableTask namespace\" }\n ,\"microsoft.durabletask/namespaces/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\n ,\"microsoft.durabletask/schedulers\": { \"SingularDisplayName\": \"Durable Task Scheduler\" }\n ,\"microsoft.durabletask/schedulers/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\n ,\"microsoft.dynamics365fraudprotection/instances\": { \"SingularDisplayName\": \"Microsoft.Dynamics365FraudProtection instance\" }\n ,\"microsoft.easm/workspaces\": { \"SingularDisplayName\": \"Microsoft Defender EASM\" }\n ,\"microsoft.edge/configurations\": { \"SingularDisplayName\": \"Site configuration\" }\n ,\"microsoft.edge/configurations/arcgatewayconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations arc gateway configuration\" }\n ,\"microsoft.edge/configurations/connectivityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations connectivity configuration\" }\n ,\"microsoft.edge/configurations/dynamicconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configuration\" }\n ,\"microsoft.edge/configurations/dynamicconfigurations/versions\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configurations version\" }\n ,\"microsoft.edge/configurations/networkconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations network configuration\" }\n ,\"microsoft.edge/configurations/securityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations security configuration\" }\n ,\"microsoft.edge/configurations/timeserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations time server configuration\" }\n ,\"microsoft.edge/connectivitystatuses\": { \"SingularDisplayName\": \"Microsoft.Edge connectivity statuse\" }\n ,\"microsoft.edge/disconnectedoperations\": { \"SingularDisplayName\": \"Azure Local - disconnected operations\" }\n ,\"microsoft.edge/siteawareresourcetypes\": { \"SingularDisplayName\": \"Microsoft.Edge site aware resource type\" }\n ,\"microsoft.edge/sites\": { \"SingularDisplayName\": \"Site manager - Azure Arc\" }\n ,\"microsoft.edge/updates\": { \"SingularDisplayName\": \"Microsoft.Edge update\" }\n ,\"microsoft.edgemarketplace/offers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace offer\" }\n ,\"microsoft.edgemarketplace/publishers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace publisher\" }\n ,\"microsoft.edgeorder/addresses\": { \"SingularDisplayName\": \"Azure Edge Hardware Center Address\" }\n ,\"microsoft.edgeorder/bootstrapconfigurations\": { \"SingularDisplayName\": \"Site Key\" }\n ,\"microsoft.edgeorder/orderitems\": { \"SingularDisplayName\": \"Azure Edge Hardware Center\" }\n ,\"microsoft.edgeorder/virtual_orderitems\": { \"SingularDisplayName\": \"Device\" }\n ,\"microsoft.edgezones/extendedzones\": { \"SingularDisplayName\": \"Microsoft.EdgeZones extended zone\" }\n ,\"microsoft.education/grants\": { \"SingularDisplayName\": \"Microsoft.Education grant\" }\n ,\"microsoft.education/labs\": { \"SingularDisplayName\": \"Microsoft.Education lab\" }\n ,\"microsoft.education/labs/joinrequests\": { \"SingularDisplayName\": \"Microsoft.Education labs join request\" }\n ,\"microsoft.education/labs/students\": { \"SingularDisplayName\": \"Microsoft.Education labs student\" }\n ,\"microsoft.education/studentlabs\": { \"SingularDisplayName\": \"Microsoft.Education student lab\" }\n ,\"microsoft.elastic/monitors\": { \"SingularDisplayName\": \"Elastic Cloud Resource\" }\n ,\"microsoft.elasticsan/elasticsans\": { \"SingularDisplayName\": \"Elastic SAN\" }\n ,\"microsoft.energydataplatform/energyservices\": { \"SingularDisplayName\": \"Microsoft.EnergyDataPlatform energy service\" }\n ,\"microsoft.enterpriseknowledgegraph/services\": { \"SingularDisplayName\": \"Microsoft.EnterpriseKnowledgeGraph service\" }\n ,\"microsoft.enterprisesupport/enterprisesupports\": { \"SingularDisplayName\": \"Microsoft.EnterpriseSupport enterprise support\" }\n ,\"microsoft.eventgrid/domains\": { \"SingularDisplayName\": \"Event Grid Domain\" }\n ,\"microsoft.eventgrid/domains/topics\": { \"SingularDisplayName\": \"Event Grid Domain Topic\" }\n ,\"microsoft.eventgrid/eventsubscriptions\": { \"SingularDisplayName\": \"Microsoft.EventGrid event subscription\" }\n ,\"microsoft.eventgrid/extensiontopics\": { \"SingularDisplayName\": \"Event Grid extension topic\" }\n ,\"microsoft.eventgrid/namespaces\": { \"SingularDisplayName\": \"Event Grid Namespace\" }\n ,\"microsoft.eventgrid/namespaces/topics\": { \"SingularDisplayName\": \"Event Grid Namespace Topic\" }\n ,\"microsoft.eventgrid/namespaces/topics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Subscription\" }\n ,\"microsoft.eventgrid/namespaces/topicspaces\": { \"SingularDisplayName\": \"Event Grid Topic Space\" }\n ,\"microsoft.eventgrid/partnerconfigurations\": { \"SingularDisplayName\": \"Event Grid Partner Configuration\" }\n ,\"microsoft.eventgrid/partnerdestinations\": { \"SingularDisplayName\": \"Event Grid Partner Destination\" }\n ,\"microsoft.eventgrid/partnernamespaces\": { \"SingularDisplayName\": \"Event Grid Partner Namespace\" }\n ,\"microsoft.eventgrid/partnernamespaces/channels\": { \"SingularDisplayName\": \"Event Grid Channel\" }\n ,\"microsoft.eventgrid/partnerregistrations\": { \"SingularDisplayName\": \"Event Grid Partner Registration\" }\n ,\"microsoft.eventgrid/partnertopics\": { \"SingularDisplayName\": \"Event Grid Partner Topic\" }\n ,\"microsoft.eventgrid/systemtopics\": { \"SingularDisplayName\": \"Event Grid System Topic\" }\n ,\"microsoft.eventgrid/systemtopics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Grid Subscriptions\" }\n ,\"microsoft.eventgrid/topics\": { \"SingularDisplayName\": \"Event Grid Topic\" }\n ,\"microsoft.eventgrid/topictypes\": { \"SingularDisplayName\": \"Microsoft.EventGrid topic type\" }\n ,\"microsoft.eventgrid/verifiedpartners\": { \"SingularDisplayName\": \"Microsoft.EventGrid verified partner\" }\n ,\"microsoft.eventhub/clusters\": { \"SingularDisplayName\": \"Event Hubs Cluster\" }\n ,\"microsoft.eventhub/namespaces\": { \"SingularDisplayName\": \"Event Hubs namespace\" }\n ,\"microsoft.eventhub/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Event Hubs Geo-DR Alias\" }\n ,\"microsoft.eventhub/namespaces/eventhubs\": { \"SingularDisplayName\": \"Event Hubs Instance\" }\n ,\"microsoft.eventhub/namespaces/providers/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\n ,\"microsoft.eventhub/namespaces/schemagroups\": { \"SingularDisplayName\": \"Schema Group\" }\n ,\"microsoft.experimentation/experimentworkspaces\": { \"SingularDisplayName\": \"Experiment Workspace\" }\n ,\"microsoft.extendedlocation/customlocations\": { \"SingularDisplayName\": \"Custom location\" }\n ,\"microsoft.fabric/capacities\": { \"SingularDisplayName\": \"Fabric Capacity\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/operationresults\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric operation result\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private endpoint connection\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private link resource\" }\n ,\"microsoft.fairfieldgardens/deviceprovisioningstates\": { \"SingularDisplayName\": \"Microsoft.FairfieldGardens device provisioning state\" }\n ,\"microsoft.fairfieldgardens/provisioningresources\": { \"SingularDisplayName\": \"Fairfield Gardens\" }\n ,\"microsoft.fairfieldgardens/provisioningresources/provisioningpolicies\": { \"SingularDisplayName\": \"Provisioning policy\" }\n ,\"microsoft.falcon/namespaces\": { \"SingularDisplayName\": \"Microsoft.Falcon namespace\" }\n ,\"microsoft.features/featureprovidernamespaces/featureconfigurations\": { \"SingularDisplayName\": \"Preview features\" }\n ,\"microsoft.fidalgo/devcenters\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenter\" }\n ,\"microsoft.fidalgo/devcenters/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters attachednetwork\" }\n ,\"microsoft.fidalgo/devcenters/catalogs\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalog\" }\n ,\"microsoft.fidalgo/devcenters/catalogs/items\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalogs item\" }\n ,\"microsoft.fidalgo/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters devboxdefinition\" }\n ,\"microsoft.fidalgo/devcenters/environmenttypes\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters environment type\" }\n ,\"microsoft.fidalgo/devcenters/galleries\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters gallery\" }\n ,\"microsoft.fidalgo/devcenters/galleries/images\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries image\" }\n ,\"microsoft.fidalgo/devcenters/galleries/images/versions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries images version\" }\n ,\"microsoft.fidalgo/devcenters/mappings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters mapping\" }\n ,\"microsoft.fidalgo/machinedefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo machinedefinition\" }\n ,\"microsoft.fidalgo/networksettings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksetting\" }\n ,\"microsoft.fidalgo/networksettings/healthchecks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksettings healthcheck\" }\n ,\"microsoft.fidalgo/projects\": { \"SingularDisplayName\": \"Microsoft.Fidalgo project\" }\n ,\"microsoft.fidalgo/projects/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects attachednetwork\" }\n ,\"microsoft.fidalgo/projects/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects devboxdefinition\" }\n ,\"microsoft.fidalgo/projects/environments\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects environment\" }\n ,\"microsoft.fidalgo/projects/pools\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects pool\" }\n ,\"microsoft.fileshares/fileshares\": { \"SingularDisplayName\": \"File share\" }\n ,\"microsoft.fluidrelay/fluidrelayservers\": { \"SingularDisplayName\": \"Fluid Relay\" }\n ,\"microsoft.footprintmonitoring/profiles\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profile\" }\n ,\"microsoft.footprintmonitoring/profiles/experiments\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles experiment\" }\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoint\" }\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints/conditions\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoints condition\" }\n ,\"microsoft.gallery/myareas/galleryitems\": { \"SingularDisplayName\": \"Template\" }\n ,\"microsoft.genomics/accounts\": { \"SingularDisplayName\": \"Genomics account\" }\n ,\"microsoft.graph/azureadapplication\": { \"SingularDisplayName\": \"Entra application\" }\n ,\"microsoft.graph/azureadapplicationprototype\": { \"SingularDisplayName\": \"Microsoft.Graph Azure ad application prototype\" }\n ,\"microsoft.graphservices/accounts\": { \"SingularDisplayName\": \"Metered API account\" }\n ,\"microsoft.guestconfiguration/guestconfigurationassignments\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignment\" }\n ,\"microsoft.guestconfiguration/guestconfigurationassignments/reports\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignments report\" }\n ,\"microsoft.hanaonazure/hanainstances\": { \"SingularDisplayName\": \"SAP HANA on Azure\" }\n ,\"microsoft.hanaonazure/sapmonitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP Solutions (classic)\" }\n ,\"microsoft.hardware/orders\": { \"SingularDisplayName\": \"Microsoft.Hardware order\" }\n ,\"microsoft.hardwaresecuritymodules/cloudhsmclusters\": { \"SingularDisplayName\": \"Azure Cloud HSM\" }\n ,\"microsoft.hdinsight/clusterpools\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster pool\" }\n ,\"microsoft.hdinsight/clusterpools/clusters\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster\" }\n ,\"microsoft.hdinsight/clusterpools/clusters/instanceviews\": { \"SingularDisplayName\": \"Microsoft.HDInsight clusterpools clusters instance view\" }\n ,\"microsoft.hdinsight/clusters\": { \"SingularDisplayName\": \"HDInsight cluster\" }\n ,\"microsoft.healthbot/healthbots\": { \"SingularDisplayName\": \"Healthcare agent service\" }\n ,\"microsoft.healthcareapis/services\": { \"SingularDisplayName\": \"Azure API for FHIR\" }\n ,\"microsoft.healthcareapis/workspaces\": { \"SingularDisplayName\": \"Health Data Services workspace\" }\n ,\"microsoft.healthcareapis/workspaces/dicomservices\": { \"SingularDisplayName\": \"DICOM service\" }\n ,\"microsoft.healthcareapis/workspaces/fhirservices\": { \"SingularDisplayName\": \"FHIR service\" }\n ,\"microsoft.healthcareapis/workspaces/iotconnectors\": { \"SingularDisplayName\": \"MedTech service\" }\n ,\"microsoft.healthdataaiservices/deidservices\": { \"SingularDisplayName\": \"De-identification Service\" }\n ,\"microsoft.healthmodel/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\n ,\"microsoft.healthplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.HealthPlatform account\" }\n ,\"microsoft.help/diagnostics\": { \"SingularDisplayName\": \"Microsoft.Help diagnostic\" }\n ,\"microsoft.help/selfhelp\": { \"SingularDisplayName\": \"Microsoft.Help self help\" }\n ,\"microsoft.help/simplifiedsolutions\": { \"SingularDisplayName\": \"Microsoft.Help simplified solution\" }\n ,\"microsoft.help/solutions\": { \"SingularDisplayName\": \"Microsoft.Help solution\" }\n ,\"microsoft.help/troubleshooters\": { \"SingularDisplayName\": \"Microsoft.Help troubleshooter\" }\n ,\"microsoft.hpcworkbench/instances\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instance\" }\n ,\"microsoft.hpcworkbench/instances/chambers\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chamber\" }\n ,\"microsoft.hpcworkbench/instances/chambers/accessprofiles\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers access profile\" }\n ,\"microsoft.hpcworkbench/instances/chambers/filerequests\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file request\" }\n ,\"microsoft.hpcworkbench/instances/chambers/files\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file\" }\n ,\"microsoft.hpcworkbench/instances/chambers/storages\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers storage\" }\n ,\"microsoft.hpcworkbench/instances/chambers/workloads\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers workload\" }\n ,\"microsoft.hpcworkbench/instances/consortiums\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances consortium\" }\n ,\"microsoft.hybridcloud/cloudconnections\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connection\" }\n ,\"microsoft.hybridcloud/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connector\" }\n ,\"microsoft.hybridcompute/arcgatewayassociatedresources\": { \"SingularDisplayName\": \"Arc gateway associated resource\" }\n ,\"microsoft.hybridcompute/arcserverwithwac\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/gateways\": { \"SingularDisplayName\": \"Arc gateway\" }\n ,\"microsoft.hybridcompute/licenses\": { \"SingularDisplayName\": \"Extended Security Updates - Windows Server 2012/R2\" }\n ,\"microsoft.hybridcompute/machines\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machines/microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\n ,\"microsoft.hybridcompute/machines/microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\n ,\"microsoft.hybridcompute/machines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.hybridcompute/machinesesu\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinespaygo\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinessoftwareassurance\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinessovereign\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Arc Private Link Scope\" }\n ,\"microsoft.hybridcompute/settings\": { \"SingularDisplayName\": \"Microsoft.HybridCompute setting\" }\n ,\"microsoft.hybridconnectivity/endpoints\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoint\" }\n ,\"microsoft.hybridconnectivity/endpoints/serviceconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoints service configuration\" }\n ,\"microsoft.hybridconnectivity/publiccloudconnectors\": { \"SingularDisplayName\": \"Multicloud connector\" }\n ,\"microsoft.hybridconnectivity/solutionconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configuration\" }\n ,\"microsoft.hybridconnectivity/solutionconfigurations/inventory\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configurations inventory\" }\n ,\"microsoft.hybridconnectivity/solutiontypes\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution type\" }\n ,\"microsoft.hybridcontainerservice/kubernetesversions\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService kubernetes version\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instance\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/agentpools\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances agent pool\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances hybrid identity metadata\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances upgrade profile\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusters\": { \"SingularDisplayName\": \"Kubernetes hybrid - Azure Arc\" }\n ,\"microsoft.hybridcontainerservice/skus\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService SKU\" }\n ,\"microsoft.hybridcontainerservice/storagespaces\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService storage space\" }\n ,\"microsoft.hybridcontainerservice/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService virtual network\" }\n ,\"microsoft.hybriddata/datamanagers\": { \"SingularDisplayName\": \"Microsoft.HybridData data manager\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data service\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definition\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions/jobs\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definitions job\" }\n ,\"microsoft.hybriddata/datamanagers/datastores\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store\" }\n ,\"microsoft.hybriddata/datamanagers/datastoretypes\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store type\" }\n ,\"microsoft.hybriddata/datamanagers/publickeys\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers public key\" }\n ,\"microsoft.hybridnetwork/configurationgroupvalues\": { \"SingularDisplayName\": \"Configuration Group Value\" }\n ,\"microsoft.hybridnetwork/devices\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Device\" }\n ,\"microsoft.hybridnetwork/networkfunctions\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Network Function\" }\n ,\"microsoft.hybridnetwork/proxypublishers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publisher\" }\n ,\"microsoft.hybridnetwork/proxypublishers/artifactstores\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers artifact store\" }\n ,\"microsoft.hybridnetwork/proxypublishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers configuration group schema\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition group\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition groups network function definition version\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design group\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design groups network service design version\" }\n ,\"microsoft.hybridnetwork/publishers\": { \"SingularDisplayName\": \"Publisher\" }\n ,\"microsoft.hybridnetwork/publishers/artifactstores\": { \"SingularDisplayName\": \"Publisher Artifact Store\" }\n ,\"microsoft.hybridnetwork/publishers/artifactstores/artifactmanifests\": { \"SingularDisplayName\": \"Publisher Artifact Manifest\" }\n ,\"microsoft.hybridnetwork/publishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Configuration Group Schema\" }\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Network Function Definition\" }\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Network Function Definition Version\" }\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Network Service Design\" }\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Network Service Design Version\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management container\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rolloutsequences\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout sequence\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rollouttiers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout tier\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specification\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollout\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts/statuses\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollouts statuse\" }\n ,\"microsoft.hybridnetwork/sitenetworkservices\": { \"SingularDisplayName\": \"Site Network Service\" }\n ,\"microsoft.hybridnetwork/sites\": { \"SingularDisplayName\": \"Site\" }\n })[tolower(id)]\n}\n", - "$fxv#10": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Prices |=========================================================================================================\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_transform_v1_2 function\n.create-or-alter function\nwith (docstring='Transforms Prices_raw into FOCUS 1.2.', folder='Prices')\nPrices_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n let prices = materialize(\n Prices_raw\n | extend PricingCurrency = coalesce(Currency, CurrencyCode) // CurrencyCode last as a fallback only\n | extend x_SkuId = coalesce(SkuId, SkuID)\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\n | extend x_SkuTerm = isoMonths(Term)\n | project-rename\n SkuMeter = MeterName,\n x_BaseUnitPrice = BasePrice,\n x_EffectivePeriodEnd = EffectiveEndDate,\n x_EffectivePeriodStart = EffectiveStartDate,\n x_PricingUnitDescription = UnitOfMeasure,\n x_SkuIncludedQuantity = IncludedQuantity,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuMeterType = MeterType,\n x_SkuOfferId = OfferID,\n x_SkuPartNumber = PartNumber,\n x_SkuPriceType = PriceType,\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTier = TierMinimumUnits\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, real(null)) // UnitPrice for savings plan is not the on-demand unit price\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, real(null)) // MarketPrice for savings plan is not the list price\n | extend ChargeCategory = case(\n x_SkuPriceType == 'Consumption', 'Usage',\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\n ''\n )\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\n //\n // Get latest ingested row based on the unique ID\n | extend x_IngestionTime = ingestion_time()\n );\n //\n // Meters for reservations and savings plans to identify commitment eligibility\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\n //\n // Copy list/base/contracted prices from on-demand SKUs\n prices\n | where x_SkuPriceType == 'SavingsPlan'\n // If we use join, specify the shuffle key\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\n //\n // Set CommitmentDiscountCategory for reuse\n | extend CommitmentDiscountCategory = case(\n x_SkuPriceType == 'ReservedInstance', 'Usage',\n x_SkuPriceType == 'SavingsPlan', 'Spend',\n ''\n )\n //\n // Calculate commitment discount eligibility\n // TODO: Would a join be faster?\n // TODO: Check this to ensure it's correct\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\n //\n // TODO: Implement x_CommitmentDiscountNormalizedRatio\n | extend x_CommitmentDiscountNormalizedRatio = real(null)\n //\n // Add PricingUnit and x_PricingBlockSize\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\n | lookup kind=leftouter (PricingUnits) on x_PricingUnitDescription\n //\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, real(null)) // Savings plan prices are for the effective price, not the contracted price\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\n | project\n BillingAccountId = tolower(case(\n BillingProfileId startswith '/', BillingProfileId,\n BillingAccountId startswith '/', BillingAccountId,\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\n )),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType = case(\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\n ''\n ),\n CommitmentDiscountUnit = case(\n isempty(CommitmentDiscountCategory), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), PricingUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', PricingUnit),\n ''\n ),\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory = case(\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed',\n ''\n ),\n PricingCurrency,\n PricingUnit,\n SkuId = coalesce(ProductId, ProductID),\n SkuMeter,\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement = case(\n strlen(x_BillingAccountId) > 32, 'MCA',\n strlen(x_BillingAccountId) < 32, 'EA',\n 'Unknown'\n ),\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingSubcategory = case(\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\n ''\n ),\n x_PricingUnitDescription,\n x_SkuDescription = Product,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\n}\n\n// Prices_final_v1_2 table\n.create-merge table Prices_final_v1_2 (\n BillingAccountId: string,\n BillingAccountName: string,\n BillingCurrency: string,\n ChargeCategory: string,\n CommitmentDiscountCategory: string,\n CommitmentDiscountType: string,\n CommitmentDiscountUnit: string,\n ContractedUnitPrice: real,\n ListUnitPrice: real,\n PricingCategory: string,\n PricingCurrency: string, // Azure\n PricingUnit: string,\n SkuId: string,\n SkuMeter: string, // Azure\n SkuPriceId: string,\n SkuPriceIdv2: string, // Hubs add-on\n x_BaseUnitPrice: real, // Azure\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure MCA\n x_BillingProfileId: string, // Azure MCA\n x_CommitmentDiscountNormalizedRatio: real, // Hubs add-on\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_ContractedUnitPriceDiscount: real, // Hubs add-on\n x_ContractedUnitPriceDiscountPercent: real, // Hubs add-on\n x_EffectivePeriodEnd: datetime, // Azure\n x_EffectivePeriodStart: datetime, // Azure\n x_EffectiveUnitPrice: real, // Azure\n x_EffectiveUnitPriceDiscount: real, // Hubs add-on\n x_EffectiveUnitPriceDiscountPercent: real, // Hubs add-on\n x_IngestionTime: datetime, // Hubs add-on\n x_PricingBlockSize: real, // Hubs add-on\n x_PricingSubcategory: string, // Hubs add-on\n x_PricingUnitDescription: string, // Azure\n x_SkuDescription: string, // Azure\n x_SkuId: string, // Azure\n x_SkuIncludedQuantity: real, // Azure EA\n x_SkuMeterCategory: string, // Azure\n x_SkuMeterId: string, // Azure\n x_SkuMeterSubcategory: string, // Azure\n x_SkuMeterType: string, // Azure\n x_SkuPriceType: string, // Azure\n x_SkuProductId: string, // Azure\n x_SkuRegion: string, // Azure\n x_SkuServiceFamily: string, // Azure\n x_SkuOfferId: string, // Azure EA\n x_SkuPartNumber: string, // Azure EA\n x_SkuTerm: int, // Azure\n x_SkuTier: real, // Azure MCA\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_TotalUnitPriceDiscount: real, // Hubs add-on\n x_TotalUnitPriceDiscountPercent: real // Hubs add-on\n)\n\n// Update policy for Prices_raw -> Prices_final_v1_2\n.alter table Prices_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Prices_raw\",\n \"Query\": \"Prices_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Cost and usage |=================================================================================================\n// Supported versions:\n// - MS: 1.2-preview, 1.0, 1.0-preview(v1)\n// https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0\n// https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024\n// https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 \n// https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All costs transformed to FOCUS 1.2.', folder='Costs')\nCosts_transform_v1_2()\n{\n let checkString = (column: string, oldValue: string, newValue: string) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n let checkInt = (column: string, oldValue: int, newValue: int) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n let checkReal = (column: string, oldValue: real, newValue: real) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n Costs_raw\n //\n // Dedupe rows\n | extend x_IngestionTime = ingestion_time()\n | extend x_ChargeId = ''\n // TODO: Consider adding a unique charge ID per row\n // hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // // 1. Resource hierarchy (including resource name), highest to lowest\n // BillingAccountId,\n // x_InvoiceSectionId,\n // x_AccountOwnerId,\n // SubAccountId,\n // x_ResourceGroupName,\n // ResourceName,\n // // 2. Resource details\n // ResourceId,\n // RegionId,\n // Tags,\n // CommitmentDiscountId,\n // x_CostCenter,\n // // 4. Meter details\n // SkuPriceId,\n // x_SkuMeterId,\n // x_SkuPartNumber,\n // x_SkuOfferId,\n // x_SkuDetails,\n // // 5. Date\n // ChargePeriodStart\n // ))\n //\n // Identify data quality issues\n // TODO: Remove x_SourceChanges in v1_3 (or later)\n | extend x_SourceChanges = trim_end(',', strcat(\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\n 'XEffectiveUnitPriceRoundingError,', ''),\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\n ))\n //\n // Handle provider columns that moved to FOCUS\n | extend PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency)\n //\n // Backup original prices/costs before the merge\n | extend old_ContractedCost = ContractedCost\n | extend old_ContractedUnitPrice = ContractedUnitPrice\n | extend old_ListCost = ListCost\n | extend old_ListUnitPrice = ListUnitPrice\n | extend old_x_EffectiveUnitPrice = x_EffectiveUnitPrice\n //\n // Fix columns needed in other changes\n | extend old_ProviderName = ProviderName, ProviderName = case(\n isnotempty(ProviderName), ProviderName,\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\n ''\n )\n //\n // Identify source\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\n ''\n ))\n // Append version check error code\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\n )\n //\n // Fix quantities\n | extend old_PricingQuantity = PricingQuantity, PricingQuantity = case(\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\n PricingQuantity\n )\n | extend old_ConsumedQuantity = ConsumedQuantity, ConsumedQuantity = case(\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\n ConsumedQuantity\n )\n //\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\n and (isempty(ListUnitPrice) or isempty(ContractedUnitPrice) or ListUnitPrice == 0 or ContractedUnitPrice == 0)\n and x_EffectiveUnitPrice != 0\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\n | as allCosts\n | where tmp_MissingPrices\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | as costsWithMissingPrices\n | join kind=leftouter (\n Prices_final_v1_2\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\n ) on tmp_ReservationPriceLookupKey\n //\n // Select the best price to use for each row\n | extend x_EffectiveUnitPrice = case(\n // If price is a rounding error away from the billed price, use the billed price\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\n // If price is a rounding error away from the contracted price, use the contracted price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\n x_EffectiveUnitPrice\n )\n | extend ContractedUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\n x_EffectiveUnitPrice\n )\n | extend ListUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // Otherwise, assume the contracted price is the same as list price to support aggregations\n ContractedUnitPrice\n )\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\n | extend ContractedCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\n // ContractedCost is 0 in all other scenarios...\n // If 0 and there's a billed cost and prices are the same, use BilledCost\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume EffectiveCost\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\n // Fall back to the original value for any unhandled scenarios\n ContractedCost\n )\n | extend ListCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\n // ListCost is 0 in all other scenarios...\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume ContractedCost\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\n // Fall back to the original value for any unhandled scenarios\n ListCost\n )\n // Merge the rest of the unmodified cost records and remove excess columns\n | union (allCosts | where not(tmp_MissingPrices))\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\n //\n | extend SkuPriceDetails = parse_json(SkuPriceDetails)\n | extend Tags = parse_json(Tags)\n | extend x_SkuDetails = parse_json(x_SkuDetails)\n //\n // Handle FOCUS 1.0-preview\n | extend old_ChargeSubcategory = ChargeSubcategory\n | extend old_ChargeCategory = ChargeCategory, ChargeCategory = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Credit', 'Credit',\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\n ChargeCategory\n )\n | extend old_ChargeClass = ChargeClass, ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass)\n //\n // Populate CapacityReservationId when not specified\n | extend CapacityReservationId = coalesce(CapacityReservationId, tostring(coalesce(x_SkuDetails.VMCapacityReservationId, SkuPriceDetails.VMCapacityReservationId, SkuPriceDetails.x_VMCapacityReservationId)))\n | extend old_CapacityReservationStatus = CapacityReservationStatus, CapacityReservationStatus = case(\n isempty(CapacityReservationId), '',\n isnotempty(CapacityReservationStatus), CapacityReservationStatus,\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\n 'Used'\n )\n //\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\n | extend old_ChargeFrequency = ChargeFrequency, ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency)\n //\n // Commitment discounts\n | extend x_CommitmentDiscountNormalizedRatio = case(\n // Calculate from CommitmentDiscountQuantity, if specified\n isnotempty(CommitmentDiscountQuantity) and CommitmentDiscountQuantity != 0, CommitmentDiscountQuantity / PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\n // Not applicable\n isempty(CommitmentDiscountStatus), real(null),\n // Parse from SKU details if not specified explicitly\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, SkuPriceDetails.RINormalizationRatio, SkuPriceDetails.x_RINormalizationRatio, dynamic(1)))\n )\n | extend old_CommitmentDiscountQuantity = CommitmentDiscountQuantity, CommitmentDiscountQuantity = case(\n // FOCUS 1.2\n isnotempty(CommitmentDiscountQuantity), CommitmentDiscountQuantity,\n // FOCUS 1.0-preview, 1.0\n isempty(CommitmentDiscountStatus), real(null),\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\n real(null)\n )\n | extend old_CommitmentDiscountUnit = CommitmentDiscountUnit, CommitmentDiscountUnit = case(\n // FOCUS 1.2\n isnotempty(CommitmentDiscountUnit), CommitmentDiscountUnit,\n // FOCUS 1.0\n isempty(CommitmentDiscountQuantity), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\n ''\n )\n | extend old_CommitmentDiscountStatus = CommitmentDiscountStatus, CommitmentDiscountStatus = case(\n // FOCUS 1.0+\n isnotempty(CommitmentDiscountStatus), CommitmentDiscountStatus,\n // FOCUS 1.0-preview\n ChargeSubcategory == 'Used Commitment', 'Used',\n ChargeSubcategory == 'Unused Commitment', 'Unused',\n ''\n )\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n //\n // Pricing\n | extend old_x_AmortizationClass = x_AmortizationClass, x_AmortizationClass = case(\n // FOCUS 1.2\n isnotempty(x_AmortizationClass), x_AmortizationClass,\n // FOCUS 1.0-preview+\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\n ''\n )\n | extend old_PricingCategory = PricingCategory, PricingCategory = case(\n // FOCUS 1.0+\n isnotempty(PricingCategory), PricingCategory,\n // FOCUS 1.0-preview\n PricingCategory == 'On-Demand', 'Standard',\n PricingCategory == 'Commitment-Based', 'Committed',\n ''\n )\n //\n // Commitment discount utilization\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n //\n // BUG: Fix ContractedCost that has bad values\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\n //\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), real(null))\n | extend old_ConsumedUnit = ConsumedUnit, ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\n //\n // Convert IDs to lowercase for consistency\n | extend BillingAccountId = tolower(BillingAccountId)\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\n //\n // BUG: Remove EffectiveCost for commitment discount purchases\n | extend old_EffectiveCost = EffectiveCost, EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), EffectiveCost)\n | extend old_x_EffectiveCostInUsd = x_EffectiveCostInUsd, x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), x_EffectiveCostInUsd)\n //\n // Clean up resource columns\n | extend old_ResourceId = ResourceId, ResourceId = case(\n isnotempty(ResourceId), ResourceId,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\n ResourceId\n )\n | extend old_ResourceName = ResourceName, ResourceName = tolower(case(\n isnotempty(ResourceName), ResourceName,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\n ResourceName\n ))\n | extend old_x_ResourceType = x_ResourceType, x_ResourceType = case(\n isnotempty(x_ResourceType), x_ResourceType,\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\n x_ResourceType\n )\n | extend old_ResourceType = ResourceType, ResourceType = case(\n // Use existing resource type display name unless it's an internal resource type ID\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\n // Use CommitmentDiscountType for commitment discount purchases\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\n // Look up display name from internal type\n isnotempty(x_ResourceType), coalesce(tostring(resource_type(x_ResourceType).SingularDisplayName), ResourceType, x_ResourceType),\n ResourceType\n )\n //\n // Handle missing values\n | extend old_PublisherName = PublisherName, PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, '')\n //\n // Handle FOCUS 1.0-preview Region column\n | extend old_Region = Region\n | extend old_RegionId = RegionId, RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region))\n | extend RegionName = coalesce(RegionName, Region)\n //\n // SKU properties\n | extend x_SkuCoreCount = toint(coalesce(SkuPriceDetails.CoreCount, SkuPriceDetails.x_VCPUs, x_SkuDetails.VCPUs, SkuPriceDetails.x_VCores, x_SkuDetails.VCores, SkuPriceDetails.x_vCores, x_SkuDetails.vCores))\n | extend x_SkuInstanceType = tostring(coalesce(SkuPriceDetails.InstanceType, SkuPriceDetails.x_ServiceType, x_SkuDetails.ServiceType, SkuPriceDetails.x_ServerSku, x_SkuDetails.ServerSku))\n | extend x_SkuOperatingSystem = case(\n isnotempty(SkuPriceDetails.OperatingSystem), SkuPriceDetails.OperatingSystem,\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Canonical', 'Linux',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType)\n )\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\n | extend SkuPriceDetails = case(\n // FOCUS 1.2\n isnotempty(SkuPriceDetails), SkuPriceDetails,\n // FOCUS 1.0-preview, 1.0\n parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\n // Prefix all keys with x_ first to avoid double-prefixing\n , @'([\\{,])\"', @'\\1\"x_')\n // CoreCount for number of CPUs/vCPUs/cores/vCores\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\n // TODO: DiskSpace for disk size in GiB\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\n // TODO: GpuCount for the number of GPUs\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\n // TODO: InstanceSeries for the size family/series\n // TODO: MemorySize for the RAM in GiB\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\n // OperatingSystem for the OS name\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\n )\n )\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\n SkuPriceDetails)\n //\n // Azure Hybrid Benefit\n | extend tmp_SqlAhb = tolower(coalesce(x_SkuDetails.AHB, SkuPriceDetails.x_AHB))\n | extend x_SkuLicenseType = case(\n ChargeCategory != 'Usage', '',\n x_SkuMeterCategory in ('Virtual Machines', 'Virtual Machine Licenses') and (x_SkuMeterSubcategory contains 'Windows' or coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL'), 'Windows Server',\n isnotempty(tmp_SqlAhb) or x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\n ''\n )\n | extend x_SkuLicenseStatus = case(\n isempty(x_SkuLicenseType), '',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL' or tmp_SqlAhb == 'true' or x_SkuMeterSubcategory contains 'Azure Hybrid Benefit', 'Enabled',\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not Enabled',\n ''\n )\n | extend x_SkuLicenseQuantity = case(\n isempty(x_SkuCoreCount) or isempty(x_SkuLicenseType), int(null),\n x_SkuCoreCount <= 8, int(8),\n x_SkuCoreCount > 8, x_SkuCoreCount,\n int(null)\n )\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\n //\n // Savings\n | extend x_CommitmentDiscountSavings = iff(isempty(ContractedCost) or ContractedCost == 0 or ContractedCost - EffectiveCost < 0.0001, real(0), ContractedCost - EffectiveCost)\n | extend x_NegotiatedDiscountSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - ContractedCost < 0.0001, real(0), ListCost - ContractedCost)\n | extend x_TotalSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - EffectiveCost < 0.0001, real(0), ListCost - EffectiveCost)\n | extend x_CommitmentDiscountPercent = iff(isempty(ContractedUnitPrice) or ContractedUnitPrice == 0 or ContractedUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\n | extend x_NegotiatedDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - ContractedUnitPrice < 0.0001, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\n | extend x_TotalDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\n //\n // Minor fixes\n | extend old_BillingPeriodEnd = BillingPeriodEnd, BillingPeriodEnd = startofmonth(BillingPeriodEnd)\n | extend old_BillingPeriodStart = BillingPeriodStart, BillingPeriodStart = startofmonth(BillingPeriodStart)\n //\n // Sort columns and apply final transforms\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n CapacityReservationId,\n CapacityReservationStatus,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountQuantity,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\n EffectiveCost,\n InvoiceId = coalesce(InvoiceId, x_InvoiceId),\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory, // TODO: Populate ServiceSubcategory from ServiceName when missing\n SkuId,\n SkuMeter,\n SkuPriceDetails,\n SkuPriceId,\n SubAccountId,\n SubAccountName = iff(isempty(SubAccountId), '', SubAccountName),\n SubAccountType,\n Tags,\n x_AccountId = iff(x_AccountId == '-2', '', x_AccountId),\n x_AccountName = iff(x_AccountId == '-2', '', x_AccountName),\n x_AccountOwnerId = iff(x_AccountId == '-2', '', x_AccountOwnerId),\n x_AmortizationClass,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement = case(\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\n ProviderName\n ),\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingItemCode,\n x_BillingItemName,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountPercent,\n x_CommitmentDiscountSavings,\n x_CommitmentDiscountSpendEligibility = '', // TODO: Add x_CommitmentDiscountSpendEligibility for Costs\n x_CommitmentDiscountUsageEligibility = '', // TODO: Add x_CommitmentDiscountUsageEligibility for Costs\n x_CommitmentDiscountUtilizationAmount,\n x_CommitmentDiscountUtilizationPotential,\n x_CommodityCode,\n x_CommodityName,\n x_ComponentName,\n x_ComponentType,\n x_ConsumedCoreHours,\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd),\n x_CostAllocationRuleName,\n x_CostCategories = parse_json(x_CostCategories),\n x_CostCenter,\n x_CostType,\n x_Credits = parse_json(x_Credits),\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount = parse_json(x_Discount),\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InstanceID,\n x_InvoiceIssuerId,\n x_InvoiceSectionId = case(\n x_InvoiceSectionId == '-2', '',\n x_InvoiceSectionId\n ),\n x_InvoiceSectionName = case(\n x_InvoiceSectionName == 'Unassigned', '',\n x_InvoiceSectionName\n ),\n x_ListCostInUsd,\n x_Location,\n x_NegotiatedDiscountPercent,\n x_NegotiatedDiscountSavings,\n x_Operation,\n x_OwnerAccountID,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription = iff(x_PricingUnitDescription == 'Unassigned', '', x_PricingUnitDescription),\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName = tolower(x_ResourceGroupName),\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServiceModel, // TODO: Populate from ServiceName when missing\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuCoreCount,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuInstanceType,\n x_SkuIsCreditEligible,\n x_SkuLicenseQuantity,\n x_SkuLicenseStatus,\n x_SkuLicenseType,\n x_SkuLicenseUnit,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOperatingSystem,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuPlanName,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceValues = bag_merge(\n checkString('BillingPeriodEnd', old_BillingPeriodEnd, BillingPeriodEnd),\n checkString('BillingPeriodStart', old_BillingPeriodStart, BillingPeriodStart),\n checkString('CapacityReservationStatus', old_CapacityReservationStatus, CapacityReservationStatus),\n checkString('ChargeCategory', old_ChargeCategory, ChargeCategory),\n checkString('ChargeClass', old_ChargeClass, ChargeClass),\n checkString('ChargeSubcategory', old_ChargeSubcategory, ''), // Not included in final schema; use empty string\n checkString('ChargeFrequency', old_ChargeFrequency, ChargeFrequency),\n checkReal('CommitmentDiscountQuantity', old_CommitmentDiscountQuantity, CommitmentDiscountQuantity),\n checkString('CommitmentDiscountUnit', old_CommitmentDiscountUnit, CommitmentDiscountUnit),\n checkString('CommitmentDiscountStatus', old_CommitmentDiscountStatus, CommitmentDiscountStatus),\n checkReal('ConsumedQuantity', old_ConsumedQuantity, ConsumedQuantity),\n checkString('ConsumedUnit', old_ConsumedUnit, ConsumedUnit),\n checkReal('ContractedCost', old_ContractedCost, ContractedCost),\n checkReal('ContractedUnitPrice', old_ContractedUnitPrice, ContractedUnitPrice),\n checkReal('EffectiveCost', old_EffectiveCost, EffectiveCost),\n checkReal('ListCost', old_ListCost, ListCost),\n checkReal('ListUnitPrice', old_ListUnitPrice, ListUnitPrice),\n checkString('PricingCategory', old_PricingCategory, PricingCategory),\n checkReal('PricingQuantity', old_PricingQuantity, PricingQuantity),\n checkString('ProviderName', old_ProviderName, ProviderName),\n checkString('PublisherName', old_PublisherName, PublisherName),\n checkString('Region', old_Region, ''), // Not included in final schema; use empty string\n checkString('RegionId', old_RegionId, RegionId),\n checkString('ResourceId', old_ResourceId, ResourceId),\n checkString('ResourceName', old_ResourceName, ResourceName),\n checkString('ResourceType', old_ResourceType, ResourceType),\n checkString('x_AmortizationClass', old_x_AmortizationClass, x_AmortizationClass),\n checkReal('x_EffectiveCostInUsd', old_x_EffectiveCostInUsd, x_EffectiveCostInUsd),\n checkReal('x_EffectiveUnitPrice', old_x_EffectiveUnitPrice, x_EffectiveUnitPrice),\n checkString('x_ResourceType', old_x_ResourceType, x_ResourceType)\n ),\n x_SourceVersion,\n x_SubproductName,\n x_TotalDiscountPercent,\n x_TotalSavings,\n x_UsageType\n}\n\n// Costs_final_v1_2 table\n.create-merge table Costs_final_v1_2 (\n AvailabilityZone: string,\n BilledCost: real,\n BillingAccountId: string,\n BillingAccountName: string,\n BillingAccountType: string,\n BillingCurrency: string,\n BillingPeriodEnd: datetime,\n BillingPeriodStart: datetime,\n CapacityReservationId: string,\n CapacityReservationStatus: string,\n ChargeCategory: string,\n ChargeClass: string,\n ChargeDescription: string,\n ChargeFrequency: string,\n ChargePeriodEnd: datetime,\n ChargePeriodStart: datetime,\n CommitmentDiscountCategory: string,\n CommitmentDiscountId: string,\n CommitmentDiscountName: string,\n CommitmentDiscountQuantity: real,\n CommitmentDiscountStatus: string,\n CommitmentDiscountType: string,\n CommitmentDiscountUnit: string,\n ConsumedQuantity: real,\n ConsumedUnit: string,\n ContractedCost: real,\n ContractedUnitPrice: real,\n EffectiveCost: real,\n InvoiceId: string,\n InvoiceIssuerName: string,\n ListCost: real,\n ListUnitPrice: real,\n PricingCategory: string,\n PricingCurrency: string,\n PricingQuantity: real,\n PricingUnit: string,\n ProviderName: string,\n PublisherName: string,\n RegionId: string,\n RegionName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n ServiceCategory: string,\n ServiceName: string,\n ServiceSubcategory: string,\n SkuId: string,\n SkuMeter: string,\n SkuPriceDetails: dynamic,\n SkuPriceId: string,\n SubAccountId: string,\n SubAccountName: string,\n SubAccountType: string,\n Tags: dynamic,\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_AmortizationClass: string, // Azure 1.2-preview+\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingItemCode: string, // Alibaba 1.0\n x_BillingItemName: string, // Alibaba 1.0\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_CommitmentDiscountNormalizedRatio: real, // Azure 1.2-preview+\n x_CommitmentDiscountPercent: real, // Hubs add-on\n x_CommitmentDiscountSavings: real, // Hubs add-on\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_CommitmentDiscountUtilizationAmount: real, // Hubs add-on\n x_CommitmentDiscountUtilizationPotential: real, // Hubs add-on\n x_CommodityCode: string, // Alibaba 1.0\n x_CommodityName: string, // Alibaba 1.0\n x_ComponentName: string, // Tencent 1.0\n x_ComponentType: string, // Tencent 1.0\n x_ConsumedCoreHours: real, // Hubs add-on\n x_ContractedCostInUsd: real, // Azure 1.0+\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_CostType: string, // GCP Jan 2024\n x_Credits: dynamic, // GCP Jan 2024\n x_CurrencyConversionRate: real, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: dynamic, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0\n x_IngestionTime: datetime, // Hubs add-on\n x_InstanceID: string, // Alibaba 1.0\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_NegotiatedDiscountPercent:real, // Hubs add-on\n x_NegotiatedDiscountSavings:real, // Hubs add-on\n x_Operation: string, // AWS 1.0\n x_OwnerAccountID: string, // Tencent 1.0\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServiceModel: string, // Azure 1.2-preview+\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuCoreCount: int, // Hubs add-on\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\n x_SkuInstanceType: string, // Hubs add-on\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuLicenseQuantity: int, // Hubs add-on\n x_SkuLicenseStatus: string, // Hubs add-on\n x_SkuLicenseType: string, // Hubs add-on\n x_SkuLicenseUnit: string, // Hubs add-on\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOperatingSystem: string, // Hubs add-on\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuPlanName: string, // Azure 1.2-preview+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceChanges: string, // Hubs add-on\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceValues: dynamic, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubproductName: string, // Tencent 1.0\n x_TotalDiscountPercent: real, // Hubs add-on\n x_TotalSavings: real, // Hubs add-on\n x_UsageType: string // AWS 1.0\n)\n\n// Update policy for Costs_raw -> Costs_final_v1_2 table\n.alter table Costs_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Costs_raw\",\n \"Query\": \"Costs_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Actual costs |===================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_transform_v1_2 function\n.create-or-alter function\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\nActualCosts_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n ActualCosts_raw\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodEnd = Date + 1d,\n ChargePeriodStart = Date,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId = '',\n SkuMeter = MeterName,\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentType = '',\n x_ComponentName = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = '',\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel,\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for ActualCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": true,\n \"Source\": \"ActualCosts_raw\",\n \"Query\": \"ActualCosts_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Amortized costs |================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_transform_v1_2 function\n.create-or-alter function\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\nAmortizedCosts_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n AmortizedCosts_raw\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodEnd = Date + 1d,\n ChargePeriodStart = Date,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId = '',\n SkuMeter = MeterName,\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentType = '',\n x_ComponentName = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = '',\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel,\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for AmortizedCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": true,\n \"Source\": \"AmortizedCosts_raw\",\n \"Query\": \"AmortizedCosts_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All commitment discount usage transformed to FOCUS 1.2. This includes reservationdeatils_raw.', folder='Commitment discounts')\nCommitmentDiscountUsage_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n CommitmentDiscountUsage_raw\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Handle resource columns\n | extend ResourceId = tolower(InstanceId)\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n //\n // Sort columns and apply final transforms\n | project\n ChargePeriodEnd = UsageDate + 1d,\n ChargePeriodStart = UsageDate,\n CommitmentDiscountCategory = 'Usage',\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\n CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\n CommitmentDiscountType = 'Reservation',\n CommitmentDiscountUnit = case(\n InstanceFlexibilityRatio == 1, 'Hours',\n InstanceFlexibilityRatio != 1, 'Normalized Hours',\n ''\n ),\n ConsumedQuantity = UsedHours,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SubAccountId,\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\n x_CommitmentDiscountCommittedAmount = ReservedHours,\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\n x_IngestionTime = ingestion_time(),\n x_ResourceGroupName,\n x_ResourceType,\n // x_RowId = hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // CommitmentDiscountId,\n // ResourceId,\n // ChargePeriodStart\n // )),\n x_ServiceModel,\n x_SkuOrderId = ReservationOrderId,\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\n}\n\n// CommitmentDiscountUsage_final_v1_2 table\n.create-merge table CommitmentDiscountUsage_final_v1_2 (\n ChargePeriodEnd: datetime, // Hubs add-on\n ChargePeriodStart: datetime, // MS 2023-03-01\n CommitmentDiscountCategory: string, // Hubs add-on\n CommitmentDiscountId: string, // MS 2023-03-01\n CommitmentDiscountQuantity: real, // MS 2023-03-01\n CommitmentDiscountType: string, // Hubs add-on\n CommitmentDiscountUnit: string, // Hubs add-on\n ConsumedQuantity: real, // MS 2023-03-01\n ProviderName: string, // Hubs add-on\n ResourceId: string, // MS 2023-03-01\n ResourceName: string, // Hubs add-on\n ResourceType: string, // Hubs add-on\n ServiceCategory: string, // Hubs add-on\n ServiceName: string, // Hubs add-on\n ServiceSubcategory: string, // Hubs add-on\n SubAccountId: string, // Hubs add-on\n x_CommitmentDiscountCommittedCount: real, // MS 2023-03-01\n x_CommitmentDiscountCommittedAmount: real, // MS 2023-03-01\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\n x_CommitmentDiscountNormalizedRatio: real, // MS 2023-03-01\n x_IngestionTime: datetime, // Hubs add-on\n x_ResourceGroupName: string, // Hubs add-on\n x_ResourceType: string, // Hubs add-on\n x_ServiceModel: string, // Hubs add-on\n x_SkuOrderId: string, // MS 2023-03-01\n x_SkuSize: string, // MS 2023-03-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string // Hubs add-on\n)\n\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_2 table\n.alter table CommitmentDiscountUsage_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"CommitmentDiscountUsage_raw\",\n \"Query\": \"CommitmentDiscountUsage_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All recommendations transformed to FOCUS 1.2.', folder='Recommendations')\nRecommendations_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Recommendations_raw\n | extend x_IngestionTime = ingestion_time()\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Convert JSON cost columns to real\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\n //\n // Build recommendation details\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\n | extend x_RecommendationDetails = case(\n // Use incoming x_RecommendationDetails first\n isnotempty(x_RecommendationDetails), x_RecommendationDetails,\n // Create one for reservation recommendations if needed\n x_SourceType == 'ReservationRecommendations', bag_pack(\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\n 'CommitmentDiscountResourceType', ResourceType,\n 'CommitmentDiscountScope', Scope,\n 'LookbackPeriodDuration', case(\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\n ''\n ),\n 'LookbackPeriodStart', FirstUsageDate,\n 'RecommendedQuantity', RecommendedQuantity,\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\n 'RegionId', Location,\n 'RegionName', RegionName,\n 'SkuMeterId', MeterId,\n 'SkuPriceDetails', SkuProperties,\n 'SkuSize', coalesce(SKU, SkuName),\n 'SkuTerm', isoMonths(Term)\n ),\n dynamic({})\n )\n //\n // Prefer specified date, then fall back to generating a date based on reservation recommendation lookback period, then validate to ensure it's not in the future\n | extend x_RecommendationDate = coalesce(x_RecommendationDate, FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d))\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\n //\n | project\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n SubAccountId = coalesce(SubAccountId, iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), '')),\n SubAccountName,\n x_EffectiveCostAfter = coalesce(x_EffectiveCostAfter, TotalCostWithReservedInstances),\n x_EffectiveCostBefore = coalesce(x_EffectiveCostBefore, CostWithNoReservedInstances),\n x_EffectiveCostSavings = coalesce(x_EffectiveCostSavings, NetSavings),\n x_IngestionTime,\n x_RecommendationCategory, // TODO: Set for reservation recommendations\n x_RecommendationDate,\n x_RecommendationDescription,\n x_RecommendationDetails,\n x_RecommendationId, // TODO: Set for reservation recommendations\n x_ResourceGroupName,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n// Recommendations_final_v1_2 table\n.create-merge table Recommendations_final_v1_2 (\n ProviderName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n SubAccountId: string,\n SubAccountName: string,\n x_EffectiveCostAfter: real,\n x_EffectiveCostBefore: real,\n x_EffectiveCostSavings: real,\n x_IngestionTime: datetime,\n x_RecommendationCategory: string,\n x_RecommendationDate: datetime,\n x_RecommendationDescription: string,\n x_RecommendationDetails: dynamic,\n x_RecommendationId: string,\n x_ResourceGroupName: string,\n x_SourceName: string,\n x_SourceProvider: string,\n x_SourceType: string,\n x_SourceVersion: string\n)\n\n// Update policy for Recommendations_raw -> Recommendations_final_v1_2 table\n.alter table Recommendations_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Recommendations_raw\",\n \"Query\": \"Recommendations_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All transactions transformed to FOCUS 1.2.', folder='Transactions')\nTransactions_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Transactions_raw\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Handle BillingPeriodStart/End\n | extend BillingMonth = tostring(BillingMonth)\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\n //\n // Sort columns and apply final transforms\n | project\n BilledCost = Amount,\n BillingAccountId = case(\n BillingProfileId startswith '/', BillingProfileId,\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\n ''\n ),\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\n BillingCurrency = Currency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory = case(\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = case(\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\n EventType == 'Refund', 'Correction',\n ''\n ),\n ChargeDescription = Description,\n ChargeFrequency = case(\n BillingFrequency == 'OneTime', 'One-Time',\n BillingFrequency == 'Recurring', 'Recurring',\n BillingFrequency\n ),\n ChargePeriodStart = EventDate,\n InvoiceId,\n PricingQuantity = Quantity,\n PricingUnit = 'Reservations',\n ProviderName,\n RegionId = Region,\n RegionName = Region,\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerEmail,\n x_CostCenter = CostCenter,\n x_InvoiceNumber = Invoice,\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\n x_IngestionTime = ingestion_time(),\n x_MonetaryCommitment = MonetaryCommitment,\n x_Overage = Overage,\n x_PurchasingBillingAccountId = PurchasingEnrollment,\n x_SkuOrderId = ReservationOrderId,\n x_SkuOrderName = ReservationOrderName,\n x_SkuSize = ArmSkuName,\n x_SkuTerm = isoMonths(Term),\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId = PurchasingSubscriptionGuid,\n x_TransactionType = EventType\n}\n\n// Transactions_final_v1_2 table\n.create-merge table Transactions_final_v1_2 (\n BilledCost: real, // MS CM EA+MCA 2023-05-01\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n ChargeCategory: string, // Hubs add-on\n ChargeClass: string, // Hubs add-on\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n InvoiceId: string, // MS CM MCA 2023-05-01\n PricingQuantity: real, // MS CM EA+MCA 2023-05-01\n PricingUnit: string, // Hubs add-on\n ProviderName: string, // Hubs add-on\n RegionId: string, // MS CM EA+MCA 2023-05-01\n RegionName: string, // MS CM EA+MCA 2023-05-01\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\n x_AccountName: string, // MS CM EA 2023-05-01\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\n x_CostCenter: string, // MS CM EA 2023-05-01\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\n x_IngestionTime: datetime, // Hubs add-on\n x_MonetaryCommitment: real, // MS CM EA 2023-05-01\n x_Overage: real, // MS CM EA 2023-05-01\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\n)\n\n// Update policy for Transactions_raw -> Transactions_final_v1_2 table\n.alter table Transactions_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Transactions_raw\",\n \"Query\": \"Transactions_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n", - "$fxv#11": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Common utility functions\n//\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\n//======================================================================================================================\n\n\n//===| Date functions |=================================================================================================\n\n// monthstring\n.create-or-alter function \nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \nmonthstring(['date']: datetime, length: int = 9)\n{\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\n}\n\n// datestring\n.create-or-alter function \nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n let month = (d: datetime) { monthstring(d, 3) };\n let endDate = iff(end == datetime('0001-01-01'), start, end);\n let sameDate = startofday(start) == startofday(endDate);\n let sameMonth = startofmonth(start) == startofmonth(endDate);\n let sameYear = startofyear(start) == startofyear(endDate);\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\n let currentYear = sameYear and startofyear(start) == startofyear(now());\n case(\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\n fullYear,\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\n // 1 full mo, same year | Mmm yyyy\n fullMonth and sameMonth and sameYear,\n strcat(month(start), ' ', getyear(start)),\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\n fullMonth and sameYear,\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\n fullMonth and not(sameYear),\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\n sameDate,\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\n not(fullMonth) and sameMonth and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\n not(fullMonth) and not(sameMonth) and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\n )\n}\n\n// daterange\n.create-or-alter function \nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n datestring(start, end)\n}\n\n// monthsago\n.create-or-alter function \nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\nmonthsago(months: int)\n{\n datetime_add('month', -months, startofmonth(now()))\n}\n\n\n//===| Number functions |===============================================================================================\n// NOTE: Must be defined before string converters\n\n// delta\n.create-or-alter function \nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \ndelta(oldval: double, newval: double)\n{\n (newval - todouble(oldval))/oldval\n}\n\n// percentOfTotal\n// NOTE: Must be before percent() function\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercentOfTotal(t: (Count: long), tot: long)\n{\n let total = todouble(tot);\n t \n | extend Percent = round(Count / total * 100, 3) \n | order by Count desc\n}\n\n// percent\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercent(t: (Count: long))\n{\n let total = todouble(toscalar(t | summarize sum(Count)));\n percentOfTotal(t, total)\n}\n\n// plusminus\n.create-or-alter function \nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\nplusminus(val: string)\n{\n let neg = substring(val, 0, 1) == '-';\n iff(neg, val, strcat('+', val))\n}\n\n// updown\n.create-or-alter function \nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\nupdown(val: string)\n{\n // TODO: Handle 0\n let neg = substring(val, 0, 1) == '-';\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\n}\n\n\n//===| String functions |===============================================================================================\n\n// percentstring\n// NOTE: Must be defined before deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\npercentstring(num: double, total: double = 1.0, places: int = 9)\n{\n let value = 1.0 * num / total * 100;\n strcat(case(\n places != 9, round(value, places),\n value < 10, round(value, 2),\n round(value, 1)\n ), '%')\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// arraystring\n.create-or-alter function \nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\narraystring(arr: dynamic)\n{\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\n tostring(arr)\n , @'^\\[\"', '')\n , @'\"\\]$', '')\n , @'^, ', '')\n , @', $', '')\n , @'^\\[]$', '')\n , '\",\"', ', ')\n}\n\n// deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\n{\n let d = delta(oldval, newval);\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\n}\n\n// diffstring\n.create-or-alter function \nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\ndiffstring(oldval: double, newval: double, places: int = 1)\n{\n plusminus(round(newval - oldval, places))\n}\n\n// numberstring\n.create-or-alter function \nwith (docstring = 'Convert a number to a string', folder = 'Common')\nnumberstring(num: double, abbrev: bool = true)\n{\n replace_regex(case(\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\n tostring(num)\n ), @'\\.0$', '')\n}\n\n\n//===| Other |==========================================================================================================\n\n// ifempty\n.create-or-alter function \nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\nifempty(val: dynamic, defaultVal: dynamic)\n{\n iff(isempty(val), defaultVal, val)\n}\n", - "$fxv#12": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / Open data functions\n// Wrap Ingestion database tables for easy access.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// PricingUnits\n.create-or-alter function\nwith (docstring = 'Gets pricing units from the FinOps toolkit PricingUnits open data.', folder = 'OpenData')\nPricingUnits()\n{\n database('Ingestion').PricingUnits\n}\n\n// Regions\n.create-or-alter function\nwith (docstring = 'Gets regions from the FinOps toolkit Regions open data.', folder = 'OpenData')\nRegion()\n{\n database('Ingestion').Regions\n}\n\n// ResourceTypes\n.create-or-alter function\nwith (docstring = 'Gets resource types from the FinOps toolkit ResourceTypes open data.', folder = 'OpenData')\nResourceType()\n{\n database('Ingestion').ResourceTypes\n}\n\n// Services\n.create-or-alter function\nwith (docstring = 'Gets services from the FinOps toolkit Services open data.', folder = 'OpenData')\nServices()\n{\n database('Ingestion').Services\n}\n", - "$fxv#13": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / FOCUS 1.0 functions\n// Used for reporting with backward compatibility.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// CommitmentDiscountUsage_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.0.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage_v1_0()\n{\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\n | union (\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\n // Convert real to decimal\n | extend\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n x_CommitmentDiscountCommittedCount = todecimal(x_CommitmentDiscountCommittedCount),\n x_CommitmentDiscountCommittedAmount = todecimal(x_CommitmentDiscountCommittedAmount),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio)\n )\n | project\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountType,\n ConsumedQuantity,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SubAccountId,\n x_CommitmentDiscountCommittedCount,\n x_CommitmentDiscountCommittedAmount,\n x_CommitmentDiscountNormalizedGroup,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountQuantity,\n x_IngestionTime,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceModel,\n x_SkuOrderId,\n x_SkuSize,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Costs_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.0.', folder = 'Costs')\nCosts_v1_0()\n{\n database('Ingestion').Costs_final_v1_0\n | union (\n database('Ingestion').Costs_final_v1_2\n // Convert real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n ContractedCost = todecimal(ContractedCost),\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n EffectiveCost = todecimal(EffectiveCost),\n ListCost = todecimal(ListCost),\n ListUnitPrice = todecimal(ListUnitPrice),\n PricingQuantity = todecimal(PricingQuantity),\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\n // Rename columns\n | project-rename\n x_InvoiceId = InvoiceId,\n x_PricingCurrency = PricingCurrency,\n x_SkuMeterName = SkuMeter\n // Generate historical x_SkuDetails format from SkuPriceDetails\n | extend x_SkuDetails = iff(isnotempty(x_SkuDetails), x_SkuDetails, parse_json(replace_regex(tostring(SkuPriceDetails), @'([\\{,])\"x_', @'\\1\"')))\n )\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost,\n ContractedUnitPrice,\n EffectiveCost,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SkuId,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType,\n Tags,\n x_AccountId,\n x_AccountName,\n x_AccountOwnerId,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_ContractedCostInUsd,\n x_CostAllocationRuleName,\n x_CostCategories,\n x_CostCenter,\n x_Credits,\n x_CostType,\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount,\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InvoiceId,\n x_InvoiceIssuerId,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_ListCostInUsd,\n x_Location,\n x_Operation,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingCurrency,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuIsCreditEligible,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_UsageType\n}\n\n\n// Prices_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all prices aligned to FOCUS 1.0.', folder = 'Prices')\nPrices_v1_0()\n{\n database('Ingestion').Prices_final_v1_0\n | union (\n database('Ingestion').Prices_final_v1_2\n // Convert real to decimal\n | extend\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n ListUnitPrice = todecimal(ListUnitPrice),\n x_BaseUnitPrice = todecimal(x_BaseUnitPrice),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\n x_ContractedUnitPriceDiscount = todecimal(x_ContractedUnitPriceDiscount),\n x_ContractedUnitPriceDiscountPercent = todecimal(x_ContractedUnitPriceDiscountPercent),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_EffectiveUnitPriceDiscount = todecimal(x_EffectiveUnitPriceDiscount),\n x_EffectiveUnitPriceDiscountPercent = todecimal(x_EffectiveUnitPriceDiscountPercent),\n x_PricingBlockSize = todecimal(x_PricingBlockSize),\n x_SkuIncludedQuantity = todecimal(x_SkuIncludedQuantity),\n x_SkuTier = todecimal(x_SkuTier),\n x_TotalUnitPriceDiscount = todecimal(x_TotalUnitPriceDiscount),\n x_TotalUnitPriceDiscountPercent = todecimal(x_TotalUnitPriceDiscountPercent) \n // Rename columns\n | project-rename\n x_PricingCurrency = PricingCurrency,\n x_SkuMeterName = SkuMeter\n )\n | project\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType,\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory,\n PricingUnit,\n SkuId,\n SkuPriceId,\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent,\n x_EffectivePeriodEnd,\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingCurrency,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_SkuDescription,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent\n}\n\n\n// Recommendations_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.0.', folder = 'Recommendations')\nRecommendations_v1_0()\n{\n database('Ingestion').Recommendations_final_v1_0\n | union (\n database('Ingestion').Recommendations_final_v1_2\n // Convert real to decimal\n | extend\n x_EffectiveCostAfter = todecimal(x_EffectiveCostAfter),\n x_EffectiveCostBefore = todecimal(x_EffectiveCostBefore),\n x_EffectiveCostSavings = todecimal(x_EffectiveCostSavings)\n )\n | project\n ProviderName,\n SubAccountId,\n x_IngestionTime,\n x_EffectiveCostAfter,\n x_EffectiveCostBefore,\n x_EffectiveCostSavings,\n x_RecommendationDate,\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Transactions_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.0.', folder = 'Transactions')\nTransactions_v1_0()\n{\n database('Ingestion').Transactions_final_v1_0\n | union (\n database('Ingestion').Transactions_final_v1_2\n // Convert real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n PricingQuantity = todecimal(PricingQuantity),\n x_MonetaryCommitment = todecimal(x_MonetaryCommitment),\n x_Overage = todecimal(x_Overage)\n // Rename columns\n | project-rename\n x_InvoiceId = InvoiceId\n )\n | project\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodStart,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n RegionId,\n RegionName,\n SubAccountId,\n SubAccountName,\n x_AccountName,\n x_AccountOwnerId,\n x_CostCenter,\n x_InvoiceId,\n x_InvoiceNumber,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_IngestionTime,\n x_MonetaryCommitment,\n x_Overage,\n x_PurchasingBillingAccountId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuSize,\n x_SkuTerm,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId,\n x_TransactionType\n}\n", - "$fxv#14": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / FOCUS 1.2 functions\n// Used for reporting with backward compatibility.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// CommitmentDiscountUsage_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.2.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage_v1_2()\n{\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\n | union (\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\n // Convert decimal to real\n | extend\n ConsumedQuantity = toreal(ConsumedQuantity),\n x_CommitmentDiscountCommittedCount = toreal(x_CommitmentDiscountCommittedCount),\n x_CommitmentDiscountCommittedAmount = toreal(x_CommitmentDiscountCommittedAmount),\n x_CommitmentDiscountNormalizedRatio = toreal(x_CommitmentDiscountNormalizedRatio)\n // Add new columns\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceSubcategory) on x_ResourceType\n | extend CommitmentDiscountQuantity = ConsumedQuantity * x_CommitmentDiscountNormalizedRatio\n | extend CommitmentDiscountUnit = case(\n x_CommitmentDiscountNormalizedRatio == 1, 'Hours',\n x_CommitmentDiscountNormalizedRatio > 1, 'Normalized Hours',\n ''\n )\n )\n | project\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountQuantity,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SubAccountId,\n x_CommitmentDiscountCommittedCount,\n x_CommitmentDiscountCommittedAmount,\n x_CommitmentDiscountNormalizedGroup,\n x_CommitmentDiscountNormalizedRatio,\n x_IngestionTime,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceModel,\n x_SkuOrderId,\n x_SkuSize,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Costs_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.2.', folder = 'Costs')\nCosts_v1_2()\n{\n database('Ingestion').Costs_final_v1_2\n | union (\n database('Ingestion').Costs_final_v1_0\n // Convert decimal to real\n | extend\n BilledCost = toreal(BilledCost),\n ConsumedQuantity = toreal(ConsumedQuantity),\n ContractedCost = toreal(ContractedCost),\n ContractedUnitPrice = toreal(ContractedUnitPrice),\n EffectiveCost = toreal(EffectiveCost),\n ListCost = toreal(ListCost),\n ListUnitPrice = toreal(ListUnitPrice),\n PricingQuantity = toreal(PricingQuantity),\n x_BilledCostInUsd = toreal(x_BilledCostInUsd),\n x_BilledUnitPrice = toreal(x_BilledUnitPrice),\n x_BillingExchangeRate = toreal(x_BillingExchangeRate),\n x_ContractedCostInUsd = toreal(x_ContractedCostInUsd),\n x_CurrencyConversionRate = toreal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = toreal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\n x_ListCostInUsd = toreal(x_ListCostInUsd),\n x_PricingBlockSize = toreal(x_PricingBlockSize)\n // Rename columns\n | project-rename\n InvoiceId = x_InvoiceId,\n PricingCurrency = x_PricingCurrency,\n SkuMeter = x_SkuMeterName\n // Add new columns\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | extend CapacityReservationId = tostring(x_SkuDetails.VMCapacityReservationId)\n | extend CapacityReservationStatus = case(\n isempty(CapacityReservationId), '',\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\n 'Used'\n )\n | extend x_CommitmentDiscountNormalizedRatio = case(\n // Not applicable\n isempty(CommitmentDiscountStatus), real(null),\n // Parse from SKU details if not specified explicitly\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, dynamic(1)))\n )\n | extend CommitmentDiscountQuantity = case(\n isempty(CommitmentDiscountStatus), real(null),\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\n real(null)\n )\n | extend CommitmentDiscountUnit = case(\n isempty(CommitmentDiscountQuantity), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\n ''\n )\n | extend x_AmortizationClass = case(\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\n ''\n )\n // Hubs add-ons\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n | extend x_SkuCoreCount = toint(coalesce(x_SkuDetails.VCPUs, x_SkuDetails.VCores, x_SkuDetails.vCores))\n | extend x_SkuInstanceType = tostring(coalesce(x_SkuDetails.ServiceType, x_SkuDetails.ServerSku))\n | extend x_SkuOperatingSystem = case(\n x_SkuDetails.ImageType == 'Canonical', 'Linux',\n x_SkuDetails.ImageType == 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\n x_SkuDetails.ImageType\n )\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\n | extend tmp_SqlAhb = tolower(x_SkuDetails.AHB)\n | extend x_SkuLicenseType = case(\n x_SkuDetails.ImageType contains 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\n ''\n )\n | extend x_SkuLicenseStatus = case(\n isnotempty(x_SkuLicenseType) or tmp_SqlAhb == 'true' or (x_SkuMeterSubcategory contains 'Azure Hybrid Benefit'), 'Enabled',\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not enabled',\n ''\n )\n | extend x_SkuLicenseQuantity = case(\n isempty(x_SkuCoreCount), int(null),\n x_SkuCoreCount <= 8, int(8),\n x_SkuCoreCount > 8, x_SkuCoreCount,\n int(null)\n )\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\n | extend x_CommitmentDiscountSavings = iff(ContractedCost < EffectiveCost, real(0), ContractedCost - EffectiveCost)\n | extend x_NegotiatedDiscountSavings = iff(ListCost < ContractedCost, real(0), ListCost - ContractedCost)\n | extend x_TotalSavings = iff(ListCost < EffectiveCost, real(0), ListCost - EffectiveCost)\n | extend x_CommitmentDiscountPercent = iff(ContractedUnitPrice == 0, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\n | extend x_NegotiatedDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\n | extend x_TotalDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\n // SkuPriceDetails conversion -- Must be after hubs add-ons\n | extend SkuPriceDetails = parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\n // Prefix all keys with x_ first to avoid double-prefixing\n , @'([\\{,])\"', @'\\1\"x_')\n // CoreCount for number of CPUs/vCPUs/cores/vCores\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\n // TODO: DiskSpace for disk size in GiB\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\n // TODO: GpuCount for the number of GPUs\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\n // TODO: InstanceSeries for the size family/series\n // TODO: MemorySize for the RAM in GiB\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\n // OperatingSystem for the OS name\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\n )\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\n SkuPriceDetails)\n )\n | extend SkuPriceDetails = iff(isnotempty(SkuPriceDetails), SkuPriceDetails, parse_json(replace_regex(tostring(x_SkuDetails), @'([\\{,])\"', @'\\1\"x_')))\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n CapacityReservationId,\n CapacityReservationStatus,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountQuantity,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost,\n ContractedUnitPrice,\n EffectiveCost,\n InvoiceId,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId,\n SkuMeter,\n SkuPriceDetails,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType,\n Tags,\n x_AccountId,\n x_AccountName,\n x_AccountOwnerId,\n x_AmortizationClass,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingItemCode,\n x_BillingItemName,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountPercent,\n x_CommitmentDiscountSavings,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_CommitmentDiscountUtilizationAmount,\n x_CommitmentDiscountUtilizationPotential,\n x_CommodityCode,\n x_CommodityName,\n x_ComponentName,\n x_ComponentType,\n x_ConsumedCoreHours,\n x_ContractedCostInUsd,\n x_CostAllocationRuleName,\n x_CostCategories,\n x_CostCenter,\n x_CostType,\n x_Credits,\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount,\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InstanceID,\n x_InvoiceIssuerId,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_ListCostInUsd,\n x_Location,\n x_NegotiatedDiscountPercent,\n x_NegotiatedDiscountSavings,\n x_Operation,\n x_OwnerAccountID,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServiceModel,\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuCoreCount,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuInstanceType,\n x_SkuIsCreditEligible,\n x_SkuLicenseQuantity,\n x_SkuLicenseStatus,\n x_SkuLicenseType,\n x_SkuLicenseUnit,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOperatingSystem,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuPlanName,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceValues,\n x_SourceVersion,\n x_SubproductName,\n x_TotalDiscountPercent,\n x_TotalSavings,\n x_UsageType\n}\n\n\n// Prices_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all prices aligned to FOCUS 1.2.', folder = 'Prices')\nPrices_v1_2()\n{\n database('Ingestion').Prices_final_v1_2\n | union (\n database('Ingestion').Prices_final_v1_0\n // Convert decimal to real\n | extend\n ContractedUnitPrice = toreal(ContractedUnitPrice),\n ListUnitPrice = toreal(ListUnitPrice),\n x_BaseUnitPrice = toreal(x_BaseUnitPrice),\n x_ContractedUnitPriceDiscount = toreal(x_ContractedUnitPriceDiscount),\n x_ContractedUnitPriceDiscountPercent = toreal(x_ContractedUnitPriceDiscountPercent),\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\n x_EffectiveUnitPriceDiscount = toreal(x_EffectiveUnitPriceDiscount),\n x_EffectiveUnitPriceDiscountPercent = toreal(x_EffectiveUnitPriceDiscountPercent),\n x_PricingBlockSize = toreal(x_PricingBlockSize),\n x_SkuIncludedQuantity = toreal(x_SkuIncludedQuantity),\n x_SkuTier = toreal(x_SkuTier),\n x_TotalUnitPriceDiscount = toreal(x_TotalUnitPriceDiscount),\n x_TotalUnitPriceDiscountPercent = toreal(x_TotalUnitPriceDiscountPercent) \n // Rename columns\n | project-rename\n PricingCurrency = x_PricingCurrency,\n SkuMeter = x_SkuMeterName\n )\n | project\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingUnit,\n SkuId,\n SkuMeter,\n SkuPriceId,\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent,\n x_EffectivePeriodEnd,\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_SkuDescription,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent\n}\n\n\n// Recommendations_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.2.', folder = 'Recommendations')\nRecommendations_v1_2()\n{\n database('Ingestion').Recommendations_final_v1_2\n | union (\n database('Ingestion').Recommendations_final_v1_0\n // Convert decimal to real\n | extend\n x_EffectiveCostAfter = toreal(x_EffectiveCostAfter),\n x_EffectiveCostBefore = toreal(x_EffectiveCostBefore),\n x_EffectiveCostSavings = toreal(x_EffectiveCostSavings)\n )\n | project\n ProviderName,\n SubAccountId,\n x_IngestionTime,\n x_EffectiveCostAfter,\n x_EffectiveCostBefore,\n x_EffectiveCostSavings,\n x_RecommendationDate,\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Transactions_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.2.', folder = 'Transactions')\nTransactions_v1_2()\n{\n database('Ingestion').Transactions_final_v1_2\n | union (\n database('Ingestion').Transactions_final_v1_0\n // Convert decimal to real\n | extend\n BilledCost = toreal(BilledCost),\n PricingQuantity = toreal(PricingQuantity),\n x_MonetaryCommitment = toreal(x_MonetaryCommitment),\n x_Overage = toreal(x_Overage)\n // Rename columns\n | project-rename\n InvoiceId = x_InvoiceId\n )\n | project\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodStart,\n InvoiceId,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n RegionId,\n RegionName,\n SubAccountId,\n SubAccountName,\n x_AccountName,\n x_AccountOwnerId,\n x_CostCenter,\n x_InvoiceNumber,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_IngestionTime,\n x_MonetaryCommitment,\n x_Overage,\n x_PurchasingBillingAccountId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuSize,\n x_SkuTerm,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId,\n x_TransactionType\n}\n\n\n//======================================================================================================================\n// Latest FOCUS version\n//======================================================================================================================\n\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage()\n{\n CommitmentDiscountUsage_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\nCosts()\n{\n Costs_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\nPrices()\n{\n Prices_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\nRecommendations()\n{\n Recommendations_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\nTransactions()\n{\n Transactions_v1_2()\n}\n", - "$fxv#15": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / Latest FOCUS version functions\n// Used for ad hoc queries.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage()\n{\n CommitmentDiscountUsage_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\nCosts()\n{\n Costs_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\nPrices()\n{\n Prices_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\nRecommendations()\n{\n Recommendations_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\nTransactions()\n{\n Transactions_v1_2()\n}\n", - "$fxv#2": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_3(id: string) {\n dynamic({\n \"microsoft.hybridnetwork/vendors\": { \"SingularDisplayName\": \"Azure Network Function Manager ? vendor\" }\n ,\"microsoft.hybridonboarding/extensionmanagers\": { \"SingularDisplayName\": \"Microsoft.HybridOnboarding extension manager\" }\n ,\"microsoft.impact/connectors\": { \"SingularDisplayName\": \"Impact Reporting Connector\" }\n ,\"microsoft.impact/impactcategories\": { \"SingularDisplayName\": \"Microsoft.Impact impact category\" }\n ,\"microsoft.impact/topologyimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact topology impact\" }\n ,\"microsoft.impact/workloadimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact workload impact\" }\n ,\"microsoft.impact/workloadimpacts/insights\": { \"SingularDisplayName\": \"Microsoft.Impact workload impacts insight\" }\n ,\"microsoft.importexport/jobs\": { \"SingularDisplayName\": \"Microsoft.ImportExport job\" }\n ,\"microsoft.insights/actiongroups\": { \"SingularDisplayName\": \"Action group\" }\n ,\"microsoft.insights/activitylogalerts\": { \"SingularDisplayName\": \"Activity log alert rule\" }\n ,\"microsoft.insights/alertrules\": { \"SingularDisplayName\": \"Microsoft.Insights alertrule\" }\n ,\"microsoft.insights/alertrules/incidents\": { \"SingularDisplayName\": \"Microsoft.insights alertrules incident\" }\n ,\"microsoft.insights/autoscalesettings\": { \"SingularDisplayName\": \"Microsoft.Insights autoscalesetting\" }\n ,\"microsoft.insights/components\": { \"SingularDisplayName\": \"Application Insights app\" }\n ,\"microsoft.insights/datacollectionendpoints\": { \"SingularDisplayName\": \"Data collection endpoint\" }\n ,\"microsoft.insights/datacollectionruleassociations\": { \"SingularDisplayName\": \"Microsoft.Insights data collection rule association\" }\n ,\"microsoft.insights/datacollectionrules\": { \"SingularDisplayName\": \"Data collection rule\" }\n ,\"microsoft.insights/datacollectionrulesresources\": { \"SingularDisplayName\": \"Data collection rule associated resource\" }\n ,\"microsoft.insights/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\n ,\"microsoft.insights/diagnosticsettingscategories\": { \"SingularDisplayName\": \"Microsoft.Insights diagnostic settings category\" }\n ,\"microsoft.insights/guestdiagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic setting\" }\n ,\"microsoft.insights/guestdiagnosticsettingsassociation\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic settings association\" }\n ,\"microsoft.insights/logprofiles\": { \"SingularDisplayName\": \"Microsoft.Insights logprofile\" }\n ,\"microsoft.insights/metricalerts\": { \"SingularDisplayName\": \"Metric alert rule\" }\n ,\"microsoft.insights/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights notification statu\" }\n ,\"microsoft.insights/privatelinkscopeoperationstatuses\": { \"SingularDisplayName\": \"Microsoft.insights private link scope operation statuse\" }\n ,\"microsoft.insights/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Monitor Private Link Scope\" }\n ,\"microsoft.insights/scheduledqueryrules\": { \"SingularDisplayName\": \"Log search alert rule\" }\n ,\"microsoft.insights/tenantactiongroups\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action group\" }\n ,\"microsoft.insights/tenantactiongroups/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action groups notification statu\" }\n ,\"microsoft.insights/vminsightsonboardingstatuses\": { \"SingularDisplayName\": \"Microsoft.Insights VM insights onboarding statuse\" }\n ,\"microsoft.insights/webtests\": { \"SingularDisplayName\": \"Application Insights availability test\" }\n ,\"microsoft.insights/workbooks\": { \"SingularDisplayName\": \"Azure Workbook\" }\n ,\"microsoft.insights/workbooktemplates\": { \"SingularDisplayName\": \"Azure Workbook Template\" }\n ,\"microsoft.integrationspaces/spaces\": { \"SingularDisplayName\": \"Integration Environment\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twin\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/assets\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins asset\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/executionplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins execution plan\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/testplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test plan\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/tests\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test\" }\n ,\"microsoft.inventory/subscriptioninternalproperties\": { \"SingularDisplayName\": \"Microsoft.Inventory subscription internal property\" }\n ,\"microsoft.iotcentral/iotapps\": { \"SingularDisplayName\": \"IoT Central Application\" }\n ,\"microsoft.iotfirmwaredefense/workspaces\": { \"SingularDisplayName\": \"Firmware analysis workspace\" }\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmware\" }\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares/summaries\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmwares summary\" }\n ,\"microsoft.iotoperations/instances\": { \"SingularDisplayName\": \"Azure IoT Operations\" }\n ,\"microsoft.iotoperations/instances/brokers\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances broker\" }\n ,\"microsoft.iotoperations/instances/brokers/authentications\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authentication\" }\n ,\"microsoft.iotoperations/instances/brokers/authorizations\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authorization\" }\n ,\"microsoft.iotoperations/instances/brokers/listeners\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers listener\" }\n ,\"microsoft.iotoperations/instances/dataflowendpoints\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow endpoint\" }\n ,\"microsoft.iotoperations/instances/dataflowprofiles\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profile\" }\n ,\"microsoft.iotoperations/instances/dataflowprofiles/dataflows\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profiles dataflow\" }\n ,\"microsoft.iotoperationsdataprocessor/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instance\" }\n ,\"microsoft.iotoperationsdataprocessor/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances dataset\" }\n ,\"microsoft.iotoperationsdataprocessor/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances pipeline\" }\n ,\"microsoft.iotoperationsmq/mq\": { \"SingularDisplayName\": \"IoT Operations Ops MQ\" }\n ,\"microsoft.iotoperationsmq/mq/broker\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker\" }\n ,\"microsoft.iotoperationsmq/mq/broker/authentication\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authentication\" }\n ,\"microsoft.iotoperationsmq/mq/broker/authorization\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authorization\" }\n ,\"microsoft.iotoperationsmq/mq/broker/listener\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker listener\" }\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector\" }\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector topic map\" }\n ,\"microsoft.iotoperationsmq/mq/diagnosticservice\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq diagnostic service\" }\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector\" }\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector topic map\" }\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector\" }\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector topic map\" }\n ,\"microsoft.iotoperationsorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator instance\" }\n ,\"microsoft.iotoperationsorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator solution\" }\n ,\"microsoft.iotoperationsorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator target\" }\n ,\"microsoft.iotsecurity/alerttypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity alert type\" }\n ,\"microsoft.iotsecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity defender setting\" }\n ,\"microsoft.iotsecurity/onpremisesensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity on premise sensor\" }\n ,\"microsoft.iotsecurity/recommendationtypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity recommendation type\" }\n ,\"microsoft.iotsecurity/sensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity sensor\" }\n ,\"microsoft.iotsecurity/sites\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity site\" }\n ,\"microsoft.keyvault/managedhsms\": { \"SingularDisplayName\": \"Azure Key Vault Managed HSM\" }\n ,\"microsoft.keyvault/vaults\": { \"SingularDisplayName\": \"Key vault\" }\n ,\"microsoft.kubernetes/connectedclusters\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc extension\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc namespace\" }\n ,\"microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\n ,\"microsoft.kubernetesconfiguration/extensiontypes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension type\" }\n ,\"microsoft.kubernetesconfiguration/extensiontypes/versions\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension types version\" }\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configuration\" }\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations/operations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configurations operation\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scope\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private endpoint connection\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private link resource\" }\n ,\"microsoft.kubernetesconfiguration/sourcecontrolconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration source control configuration\" }\n ,\"microsoft.kubernetesruntime/bgppeers\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime bgp peer\" }\n ,\"microsoft.kubernetesruntime/loadbalancers\": { \"SingularDisplayName\": \"Arc Load Balancer\" }\n ,\"microsoft.kubernetesruntime/services\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime service\" }\n ,\"microsoft.kubernetesruntime/storageclasses\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime storage class\" }\n ,\"microsoft.kusto/clusters\": { \"SingularDisplayName\": \"Azure Data Explorer Cluster\" }\n ,\"microsoft.kusto/clusters/databases\": { \"SingularDisplayName\": \"Azure Data Explorer Database\" }\n ,\"microsoft.labservices/labaccounts\": { \"SingularDisplayName\": \"Lab account\" }\n ,\"microsoft.labservices/labaccounts/labs\": { \"SingularDisplayName\": \"Lab\" }\n ,\"microsoft.labservices/labplans\": { \"SingularDisplayName\": \"Lab plan\" }\n ,\"microsoft.labservices/labs\": { \"SingularDisplayName\": \"Lab\" }\n ,\"microsoft.liftrpilot/organizations\": { \"SingularDisplayName\": \"Azure Pilot\" }\n ,\"microsoft.loadtestservice/loadtestmappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test mapping\" }\n ,\"microsoft.loadtestservice/loadtestprofilemappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test profile mapping\" }\n ,\"microsoft.loadtestservice/loadtests\": { \"SingularDisplayName\": \"Azure Load Testing\" }\n ,\"microsoft.loadtestservice/playwrightworkspaces\": { \"SingularDisplayName\": \"Playwright Workspace\" }\n ,\"microsoft.logic/businessprocesses\": { \"SingularDisplayName\": \"Business Process\" }\n ,\"microsoft.logic/integrationaccounts\": { \"SingularDisplayName\": \"Logic app integration account\" }\n ,\"microsoft.logic/integrationserviceenvironments\": { \"SingularDisplayName\": \"Integration Service Environment\" }\n ,\"microsoft.logic/integrationserviceenvironments/health\": { \"SingularDisplayName\": \"Microsoft.Logic integration service environments health\" }\n ,\"microsoft.logic/integrationserviceenvironments/managedapis\": { \"SingularDisplayName\": \"Managed Connector\" }\n ,\"microsoft.logic/templates\": { \"SingularDisplayName\": \"Logic Apps Template\" }\n ,\"microsoft.logic/workflows\": { \"SingularDisplayName\": \"Logic app\" }\n ,\"microsoft.logz/monitors\": { \"SingularDisplayName\": \"Logz.io\" }\n ,\"microsoft.logz/monitors/accounts\": { \"SingularDisplayName\": \"Logz sub account\" }\n ,\"microsoft.m365/m365resources\": { \"SingularDisplayName\": \"Microsoft.M365 m365 resource\" }\n ,\"microsoft.m365consumptionservices/services\": { \"SingularDisplayName\": \"Microsoft.M365ConsumptionServices service\" }\n ,\"microsoft.machinelearning/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plan\" }\n ,\"microsoft.machinelearning/commitmentplans/commitmentassociations\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plans commitment association\" }\n ,\"microsoft.machinelearning/webservices\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) web service\" }\n ,\"microsoft.machinelearning/workspaces\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) workspace\" }\n ,\"microsoft.machinelearningexperimentation/accounts\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation account\" }\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspace\" }\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspaces project\" }\n ,\"microsoft.machinelearningservices/aistudio\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.machinelearningservices/aistudiocreate\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.machinelearningservices/registries\": { \"SingularDisplayName\": \"Azure Machine Learning registry\" }\n ,\"microsoft.machinelearningservices/workspaces\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints\": { \"SingularDisplayName\": \"Machine learning online endpoint\" }\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints/deployments\": { \"SingularDisplayName\": \"Machine learning online deployment\" }\n ,\"microsoft.machinelearningservices/workspacescreate\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\n ,\"microsoft.maintenance/configurationassignments\": { \"SingularDisplayName\": \"Microsoft.Maintenance configuration assignment\" }\n ,\"microsoft.maintenance/maintenanceconfigurations\": { \"SingularDisplayName\": \"Maintenance Configuration\" }\n ,\"microsoft.maintenance/maintenanceconfigurationsaumbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\n ,\"microsoft.maintenance/maintenanceconfigurationsbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\n ,\"microsoft.maintenance/publicmaintenanceconfigurations\": { \"SingularDisplayName\": \"Microsoft.Maintenance public maintenance configuration\" }\n ,\"microsoft.managedidentity/identities\": { \"SingularDisplayName\": \"Microsoft.ManagedIdentity identity\" }\n ,\"microsoft.managedidentity/userassignedidentities\": { \"SingularDisplayName\": \"Managed Identity\" }\n ,\"microsoft.managednetwork/managednetworks\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed network\" }\n ,\"microsoft.managednetwork/managednetworks/managednetworkgroups\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network group\" }\n ,\"microsoft.managednetwork/managednetworks/managednetworkpeeringpolicies\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network peering policy\" }\n ,\"microsoft.managednetworkfabric/accesscontrollists\": { \"SingularDisplayName\": \"Access Control List (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/internetgatewayrules\": { \"SingularDisplayName\": \"Internet Gateway Rule (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/internetgateways\": { \"SingularDisplayName\": \"Internet Gateway (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipcommunities\": { \"SingularDisplayName\": \"IP Community (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipextendedcommunities\": { \"SingularDisplayName\": \"IP Extended Community (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipprefixes\": { \"SingularDisplayName\": \"IP Prefix (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l2isolationdomains\": { \"SingularDisplayName\": \"Layer 2 Isolation Domain (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains\": { \"SingularDisplayName\": \"Layer 3 Isolation Domain (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains/externalnetworks\": { \"SingularDisplayName\": \"External Network (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains/internalnetworks\": { \"SingularDisplayName\": \"Internal Network (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/neighborgroups\": { \"SingularDisplayName\": \"Neighbor Group (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkdevices\": { \"SingularDisplayName\": \"Network Device (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkdevices/networkinterfaces\": { \"SingularDisplayName\": \"Network Interface (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabriccontrollers\": { \"SingularDisplayName\": \"Network Fabric Controller (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabrics\": { \"SingularDisplayName\": \"Network Fabric (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabrics/networktonetworkinterconnects\": { \"SingularDisplayName\": \"Network to Network Interconnect (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabricskus\": { \"SingularDisplayName\": \"Network Fabric SKU (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkmonitors\": { \"SingularDisplayName\": \"Microsoft.ManagedNetworkFabric network monitor\" }\n ,\"microsoft.managednetworkfabric/networkpacketbrokers\": { \"SingularDisplayName\": \"Network Packet Broker (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkracks\": { \"SingularDisplayName\": \"Network Rack (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networktaprules\": { \"SingularDisplayName\": \"Network Tap Rule (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networktaps\": { \"SingularDisplayName\": \"Network Tap (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/routepolicies\": { \"SingularDisplayName\": \"Route Policy (Operator Nexus)\" }\n ,\"microsoft.managedservices/marketplaceregistrationdefinitions\": { \"SingularDisplayName\": \"Microsoft.ManagedServices marketplace registration definition\" }\n ,\"microsoft.managedservices/registrationassignments\": { \"SingularDisplayName\": \"Microsoft.ManagedServices registration assignment\" }\n ,\"microsoft.managedservices/registrationdefinitions\": { \"SingularDisplayName\": \"Azure Lighthouse\" }\n ,\"microsoft.management/managementgroups\": { \"SingularDisplayName\": \"Microsoft.Management management group\" }\n ,\"microsoft.management/managementgroups/microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\n ,\"microsoft.management/managementgroups/providers/privatelinkassociations\": { \"SingularDisplayName\": \"Application Gateway\" }\n ,\"microsoft.management/managementgroups/providers/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\n ,\"microsoft.management/managementgroups/settings\": { \"SingularDisplayName\": \"Microsoft.Management management groups setting\" }\n ,\"microsoft.management/managementgroups/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Management management groups subscription\" }\n ,\"microsoft.management/servicegroups\": { \"SingularDisplayName\": \"Service group\" }\n ,\"microsoft.managementpartner/partners\": { \"SingularDisplayName\": \"Microsoft.ManagementPartner partner\" }\n ,\"microsoft.manufacturingplatform/manufacturingdataservices\": { \"SingularDisplayName\": \"Factory Operations Agent in Azure AI Foundry\" }\n ,\"microsoft.maps/accounts\": { \"SingularDisplayName\": \"Azure Maps Account\" }\n ,\"microsoft.maps/accounts/creators\": { \"SingularDisplayName\": \"Azure Maps Creator Resource\" }\n ,\"microsoft.marketplace/privatestores\": { \"SingularDisplayName\": \"Microsoft.Marketplace private store\" }\n ,\"microsoft.marketplace/privatestores/adminrequestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores admin request approval\" }\n ,\"microsoft.marketplace/privatestores/collections\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collection\" }\n ,\"microsoft.marketplace/privatestores/collections/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collections offer\" }\n ,\"microsoft.marketplace/privatestores/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores offer\" }\n ,\"microsoft.marketplace/privatestores/requestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores request approval\" }\n ,\"microsoft.media/mediaservices\": { \"SingularDisplayName\": \"Media service\" }\n ,\"microsoft.media/mediaservices/accountfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services account filter\" }\n ,\"microsoft.media/mediaservices/assets\": { \"SingularDisplayName\": \"Microsoft.Media media services asset\" }\n ,\"microsoft.media/mediaservices/assets/assetfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services assets asset filter\" }\n ,\"microsoft.media/mediaservices/assets/tracks\": { \"SingularDisplayName\": \"Microsoft.Media media services assets track\" }\n ,\"microsoft.media/mediaservices/assets/tracks/operationresults\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation result\" }\n ,\"microsoft.media/mediaservices/assets/tracks/operationstatuses\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation statuse\" }\n ,\"microsoft.media/mediaservices/contentkeypolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services content key policy\" }\n ,\"microsoft.media/mediaservices/liveevents\": { \"SingularDisplayName\": \"Live event\" }\n ,\"microsoft.media/mediaservices/liveevents/liveoutputs\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices live events live output\" }\n ,\"microsoft.media/mediaservices/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private endpoint connection\" }\n ,\"microsoft.media/mediaservices/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private link resource\" }\n ,\"microsoft.media/mediaservices/streamingendpoints\": { \"SingularDisplayName\": \"Streaming Endpoint\" }\n ,\"microsoft.media/mediaservices/streaminglocators\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming locator\" }\n ,\"microsoft.media/mediaservices/streamingpolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming policy\" }\n ,\"microsoft.media/mediaservices/transforms\": { \"SingularDisplayName\": \"Microsoft.Media media services transform\" }\n ,\"microsoft.media/mediaservices/transforms/jobs\": { \"SingularDisplayName\": \"Microsoft.Media media services transforms job\" }\n ,\"microsoft.mesh/worlds\": { \"SingularDisplayName\": \"Microsoft.Mesh world\" }\n ,\"microsoft.mesh/worlds/events\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds event\" }\n ,\"microsoft.mesh/worlds/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds events access policy\" }\n ,\"microsoft.mesh/worlds/spaces\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds space\" }\n ,\"microsoft.mesh/worlds/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds spaces access policy\" }\n ,\"microsoft.mesh/worlds/templates\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds template\" }\n ,\"microsoft.mesh/worlds/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds templates access policy\" }\n ,\"microsoft.messagingcatalog/catalogs\": { \"SingularDisplayName\": \"Microsoft.MessagingCatalog catalog\" }\n ,\"microsoft.messagingconnectors/connectors\": { \"SingularDisplayName\": \"Microsoft.MessagingConnectors connector\" }\n ,\"microsoft.metaverse/metaverses\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverse\" }\n ,\"microsoft.metaverse/metaverses/events\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses event\" }\n ,\"microsoft.metaverse/metaverses/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses events access policy\" }\n ,\"microsoft.metaverse/metaverses/spaces\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses space\" }\n ,\"microsoft.metaverse/metaverses/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses spaces access policy\" }\n ,\"microsoft.metaverse/metaverses/templates\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses template\" }\n ,\"microsoft.metaverse/metaverses/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses templates access policy\" }\n ,\"microsoft.migrate/assessmentprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment project\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/clusters\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments cluster\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/assessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment\" }\n ,\"microsoft.migrate/assessmentprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/assessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments avs assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business case\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/avssummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases avs summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedavsmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated avs machine\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated machine\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedsqlentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated sql entity\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated web app\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/iaassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases iaas summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/overviewsummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases overview summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/paassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases paas summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects group\" }\n ,\"microsoft.migrate/assessmentprojects/groups/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessments assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessments avs assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql database\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql instance\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/recommendedassessedentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments recommended assessed entity\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments web app service plan\" }\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessment\" }\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/hypervcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects hypervcollector\" }\n ,\"microsoft.migrate/assessmentprojects/importcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects importcollector\" }\n ,\"microsoft.migrate/assessmentprojects/importsqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects import sql collector\" }\n ,\"microsoft.migrate/assessmentprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects machine\" }\n ,\"microsoft.migrate/assessmentprojects/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private endpoint connection\" }\n ,\"microsoft.migrate/assessmentprojects/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private link resource\" }\n ,\"microsoft.migrate/assessmentprojects/projectsummary\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects project summary\" }\n ,\"microsoft.migrate/assessmentprojects/servercollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects servercollector\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql database\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql instance\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql machine\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/sqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sqlcollector\" }\n ,\"microsoft.migrate/assessmentprojects/vmwarecollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects vmwarecollector\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments web app service plan\" }\n ,\"microsoft.migrate/assessmentprojects/webappcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app collector\" }\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessment\" }\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessments summary\" }\n ,\"microsoft.migrate/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate project\" }\n ,\"microsoft.migrate/migrateprojects/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database instance\" }\n ,\"microsoft.migrate/migrateprojects/databases\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database\" }\n ,\"microsoft.migrate/migrateprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects machine\" }\n ,\"microsoft.migrate/migrateprojects/migrateevents\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects migrate event\" }\n ,\"microsoft.migrate/migrateprojects/solutions\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects solution\" }\n ,\"microsoft.migrate/modernizeprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize project\" }\n ,\"microsoft.migrate/modernizeprojects/deployedresources\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects deployed resource\" }\n ,\"microsoft.migrate/modernizeprojects/jobs\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects job\" }\n ,\"microsoft.migrate/modernizeprojects/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects jobs operation\" }\n ,\"microsoft.migrate/modernizeprojects/migrateagents\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agent\" }\n ,\"microsoft.migrate/modernizeprojects/migrateagents/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agents operation\" }\n ,\"microsoft.migrate/modernizeprojects/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects operation\" }\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployment\" }\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployments operation\" }\n ,\"microsoft.migrate/modernizeprojects/workloadinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instance\" }\n ,\"microsoft.migrate/modernizeprojects/workloadinstances/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instances operation\" }\n ,\"microsoft.migrate/movecollections\": { \"SingularDisplayName\": \"Microsoft.Migrate move collection\" }\n ,\"microsoft.migrate/movecollections/moveresources\": { \"SingularDisplayName\": \"Microsoft.Migrate move collections move resource\" }\n ,\"microsoft.migrate/projects\": { \"SingularDisplayName\": \"Migration project\" }\n ,\"microsoft.mission/approvals\": { \"SingularDisplayName\": \"Approval\" }\n ,\"microsoft.mission/catalogs\": { \"SingularDisplayName\": \"Catalog\" }\n ,\"microsoft.mission/communities\": { \"SingularDisplayName\": \"Community\" }\n ,\"microsoft.mission/communities/communityendpoints\": { \"SingularDisplayName\": \"Community endpoint\" }\n ,\"microsoft.mission/communities/transithubs\": { \"SingularDisplayName\": \"Transit hub\" }\n ,\"microsoft.mission/enclaveconnections\": { \"SingularDisplayName\": \"Enclave connection\" }\n ,\"microsoft.mission/externalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission external connection\" }\n ,\"microsoft.mission/internalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission internal connection\" }\n ,\"microsoft.mission/virtualenclaves\": { \"SingularDisplayName\": \"Enclave\" }\n ,\"microsoft.mission/virtualenclaves/enclaveendpoints\": { \"SingularDisplayName\": \"Enclave endpoint\" }\n ,\"microsoft.mission/virtualenclaves/endpoints\": { \"SingularDisplayName\": \"Endpoint\" }\n ,\"microsoft.mission/virtualenclaves/workloads\": { \"SingularDisplayName\": \"Workload\" }\n ,\"microsoft.mixedreality/objectanchorsaccounts\": { \"SingularDisplayName\": \"Object Anchors Account\" }\n ,\"microsoft.mixedreality/objectunderstandingaccounts\": { \"SingularDisplayName\": \"Object Understanding Account\" }\n ,\"microsoft.mixedreality/remoterenderingaccounts\": { \"SingularDisplayName\": \"Remote Rendering Account\" }\n ,\"microsoft.mixedreality/spatialanchorsaccounts\": { \"SingularDisplayName\": \"Spatial Anchors Account\" }\n ,\"microsoft.mixedreality/spatialmapsaccounts\": { \"SingularDisplayName\": \"Microsoft.MixedReality spatial maps account\" }\n ,\"microsoft.mobilenetwork/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork amf deployment\" }\n ,\"microsoft.mobilenetwork/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork cluster service\" }\n ,\"microsoft.mobilenetwork/mobilenetworks\": { \"SingularDisplayName\": \"Mobile Network\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/datanetworks\": { \"SingularDisplayName\": \"Data Network\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/services\": { \"SingularDisplayName\": \"Service\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/simpolicies\": { \"SingularDisplayName\": \"SIM Policy\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/sites\": { \"SingularDisplayName\": \"Mobile Network Site\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/slices\": { \"SingularDisplayName\": \"Slice\" }\n ,\"microsoft.mobilenetwork/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nrf deployment\" }\n ,\"microsoft.mobilenetwork/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nssf deployment\" }\n ,\"microsoft.mobilenetwork/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork observability service\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes\": { \"SingularDisplayName\": \"Packet Core Control Plane\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes\": { \"SingularDisplayName\": \"Packet Core Data Plane\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes/attacheddatanetworks\": { \"SingularDisplayName\": \"Attached Data Network\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplaneversions\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork packet core control plane version\" }\n ,\"microsoft.mobilenetwork/radioaccessnetworks\": { \"SingularDisplayName\": \"Radio Access Network Insights\" }\n ,\"microsoft.mobilenetwork/sdmdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sdm deployment\" }\n ,\"microsoft.mobilenetwork/simgroups\": { \"SingularDisplayName\": \"SIM Group\" }\n ,\"microsoft.mobilenetwork/simgroups/sims\": { \"SingularDisplayName\": \"SIM\" }\n ,\"microsoft.mobilenetwork/sims\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sim\" }\n ,\"microsoft.mobilenetwork/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork smf deployment\" }\n ,\"microsoft.mobilenetwork/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork upf deployment\" }\n ,\"microsoft.mobilenetwork/virtualizedmmedeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork virtualized mme deployment\" }\n ,\"microsoft.mobilenetwork/vnfagentdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork vnf agent deployment\" }\n ,\"microsoft.mobilepacketcore/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore amf deployment\" }\n ,\"microsoft.mobilepacketcore/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore cluster service\" }\n ,\"microsoft.mobilepacketcore/networkfunctions\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore network function\" }\n ,\"microsoft.mobilepacketcore/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nrf deployment\" }\n ,\"microsoft.mobilepacketcore/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nssf deployment\" }\n ,\"microsoft.mobilepacketcore/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore observability service\" }\n ,\"microsoft.mobilepacketcore/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore smf deployment\" }\n ,\"microsoft.mobilepacketcore/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore upf deployment\" }\n ,\"microsoft.modsimworkbench/workbenches\": { \"SingularDisplayName\": \"Modeling and Simulation Workbench\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers\": { \"SingularDisplayName\": \"Chamber\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/connectors\": { \"SingularDisplayName\": \"Chamber Connector\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/filerequests\": { \"SingularDisplayName\": \"Chamber Data Pipeline File Request\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/files\": { \"SingularDisplayName\": \"Chamber Data Pipeline File\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/licenses\": { \"SingularDisplayName\": \"Chamber License\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/storages\": { \"SingularDisplayName\": \"Chamber Storage\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/workloads\": { \"SingularDisplayName\": \"Chamber VM\" }\n ,\"microsoft.modsimworkbench/workbenches/sharedstorages\": { \"SingularDisplayName\": \"Shared Storage\" }\n ,\"microsoft.monitor/accounts\": { \"SingularDisplayName\": \"Azure Monitor workspace\" }\n ,\"microsoft.monitor/investigations\": { \"SingularDisplayName\": \"Microsoft.Monitor investigation\" }\n ,\"microsoft.monitor/pipelinegroups\": { \"SingularDisplayName\": \"Azure Monitor pipeline\" }\n ,\"microsoft.mysqldiscovery/mysqlsites\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsite\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites agent\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites error summary\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/mysqlservers\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites my sqlserver\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/summaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites summary\" }\n ,\"microsoft.netapp/netappaccounts\": { \"SingularDisplayName\": \"NetApp account\" }\n ,\"microsoft.netapp/netappaccounts/backuppolicies\": { \"SingularDisplayName\": \"Backup Policy\" }\n ,\"microsoft.netapp/netappaccounts/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools\": { \"SingularDisplayName\": \"Capacity pool\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes\": { \"SingularDisplayName\": \"Volume\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/volumequotarules\": { \"SingularDisplayName\": \"User and group quota\" }\n ,\"microsoft.netapp/netappaccounts/snapshotpolicies\": { \"SingularDisplayName\": \"Snapshot policy\" }\n ,\"microsoft.netapp/netappaccounts/volumegroups\": { \"SingularDisplayName\": \"VolumeGroup\" }\n ,\"microsoft.network/applicationgatewayavailablessloptions\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl option\" }\n ,\"microsoft.network/applicationgatewayavailablessloptions/predefinedpolicies\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl options predefined policy\" }\n ,\"microsoft.network/applicationgateways\": { \"SingularDisplayName\": \"Application gateway\" }\n ,\"microsoft.network/applicationgatewaywebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Application Gateway WAF policy\" }\n ,\"microsoft.network/applicationsecuritygroups\": { \"SingularDisplayName\": \"Application security group\" }\n ,\"microsoft.network/azurefirewalls\": { \"SingularDisplayName\": \"Firewall\" }\n ,\"microsoft.network/azurewebcategories\": { \"SingularDisplayName\": \"Microsoft.Network Azure web category\" }\n ,\"microsoft.network/bastionhosts\": { \"SingularDisplayName\": \"Bastion\" }\n ,\"microsoft.network/cloudserviceslots\": { \"SingularDisplayName\": \"Microsoft.Network cloud service slot\" }\n ,\"microsoft.network/connections\": { \"SingularDisplayName\": \"Connection\" }\n ,\"microsoft.network/customipprefixes\": { \"SingularDisplayName\": \"Custom IP Prefix\" }\n ,\"microsoft.network/ddoscustompolicies\": { \"SingularDisplayName\": \"Microsoft.Network DDoS custom policy\" }\n ,\"microsoft.network/ddosprotectionplans\": { \"SingularDisplayName\": \"DDoS protection plan\" }\n ,\"microsoft.network/dnsforwardingrulesets\": { \"SingularDisplayName\": \"DNS forwarding ruleset\" }\n ,\"microsoft.network/dnsresolverdomainlists\": { \"SingularDisplayName\": \"DNS Domain List\" }\n ,\"microsoft.network/dnsresolverpolicies\": { \"SingularDisplayName\": \"DNS Security Policy\" }\n ,\"microsoft.network/dnsresolvers\": { \"SingularDisplayName\": \"DNS private resolver\" }\n ,\"microsoft.network/dnszones\": { \"SingularDisplayName\": \"DNS zone\" }\n ,\"microsoft.network/dscpconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network DSCP configuration\" }\n ,\"microsoft.network/expressroutecircuits\": { \"SingularDisplayName\": \"ExpressRoute circuit\" }\n ,\"microsoft.network/expressroutecrossconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connection\" }\n ,\"microsoft.network/expressroutecrossconnections/peerings\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connections peering\" }\n ,\"microsoft.network/expressroutegateways\": { \"SingularDisplayName\": \"ExpressRoute Gateway\" }\n ,\"microsoft.network/expressroutegateways/expressrouteconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route gateways express route connection\" }\n ,\"microsoft.network/expressrouteports\": { \"SingularDisplayName\": \"ExpressRoute Direct\" }\n ,\"microsoft.network/expressrouteportslocations\": { \"SingularDisplayName\": \"Microsoft.Network express route ports location\" }\n ,\"microsoft.network/firewallpolicies\": { \"SingularDisplayName\": \"Firewall Policy\" }\n ,\"microsoft.network/frontdoors\": { \"SingularDisplayName\": \"Front Door and CDN profiles\" }\n ,\"microsoft.network/frontdoorwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Front Door WAF policy\" }\n ,\"microsoft.network/ipallocations\": { \"SingularDisplayName\": \"Microsoft.Network IP allocation\" }\n ,\"microsoft.network/ipgroups\": { \"SingularDisplayName\": \"IP Group\" }\n ,\"microsoft.network/loadbalancers\": { \"SingularDisplayName\": \"Load balancer\" }\n ,\"microsoft.network/localnetworkgateways\": { \"SingularDisplayName\": \"Local network gateway\" }\n ,\"microsoft.network/natgateways\": { \"SingularDisplayName\": \"NAT gateway\" }\n ,\"microsoft.network/networkexperimentprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profile\" }\n ,\"microsoft.network/networkexperimentprofiles/experiments\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profiles experiment\" }\n ,\"microsoft.network/networkinterfaces\": { \"SingularDisplayName\": \"Network interface\" }\n ,\"microsoft.network/networkmanagerconnections\": { \"SingularDisplayName\": \"Microsoft.Network network manager connection\" }\n ,\"microsoft.network/networkmanagers\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/connectivityconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/ipampools\": { \"SingularDisplayName\": \"IP address pool\" }\n ,\"microsoft.network/networkmanagers/networkgroups\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/routingconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/securityadminconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/securityuserconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/verifierworkspaces\": { \"SingularDisplayName\": \"Verifier Workspace\" }\n ,\"microsoft.network/networkprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network profile\" }\n ,\"microsoft.network/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group\" }\n ,\"microsoft.network/networksecurityperimeters\": { \"SingularDisplayName\": \"Network Security Perimeter\" }\n ,\"microsoft.network/networksecurityperimeters/profiles\": { \"SingularDisplayName\": \"Network Security Perimeter Profile\" }\n ,\"microsoft.network/networkverifiers\": { \"SingularDisplayName\": \"Virtual Network Verifier\" }\n ,\"microsoft.network/networkvirtualappliances\": { \"SingularDisplayName\": \"Microsoft.Network network virtual appliance\" }\n ,\"microsoft.network/networkwatchers\": { \"SingularDisplayName\": \"Network Watcher\" }\n ,\"microsoft.network/networkwatchers/flowlogs\": { \"SingularDisplayName\": \"Flow log\" }\n ,\"microsoft.network/p2svpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Point to Site)\" }\n ,\"microsoft.network/privatednszones\": { \"SingularDisplayName\": \"Private DNS zone\" }\n ,\"microsoft.network/privatednszones/virtualnetworklinks\": { \"SingularDisplayName\": \"Virtual network link\" }\n ,\"microsoft.network/privateendpoints\": { \"SingularDisplayName\": \"Private endpoint\" }\n ,\"microsoft.network/privatelinkservices\": { \"SingularDisplayName\": \"Private link service\" }\n ,\"microsoft.network/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\n ,\"microsoft.network/publicipprefixes\": { \"SingularDisplayName\": \"Public IP Prefix\" }\n ,\"microsoft.network/routefilters\": { \"SingularDisplayName\": \"Route filter\" }\n ,\"microsoft.network/routetables\": { \"SingularDisplayName\": \"Route table\" }\n ,\"microsoft.network/securitypartnerproviders\": { \"SingularDisplayName\": \"Microsoft.Network security partner provider\" }\n ,\"microsoft.network/serviceendpointpolicies\": { \"SingularDisplayName\": \"Service endpoint policy\" }\n ,\"microsoft.network/trafficmanagergeographichierarchies\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager geographic hierarchy\" }\n ,\"microsoft.network/trafficmanagerprofiles\": { \"SingularDisplayName\": \"Traffic Manager profile\" }\n ,\"microsoft.network/trafficmanagerusermetricskeys\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager user metrics key\" }\n ,\"microsoft.network/virtualhubs\": { \"SingularDisplayName\": \"Microsoft.Network/virtualHub\" }\n ,\"microsoft.network/virtualnetworkgateways\": { \"SingularDisplayName\": \"Virtual network gateway\" }\n ,\"microsoft.network/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network\" }\n ,\"microsoft.network/virtualnetworktaps\": { \"SingularDisplayName\": \"Virtual network terminal access point\" }\n ,\"microsoft.network/virtualrouters\": { \"SingularDisplayName\": \"Microsoft.Network virtual router\" }\n ,\"microsoft.network/virtualrouters/peerings\": { \"SingularDisplayName\": \"Microsoft.Network virtual routers peering\" }\n ,\"microsoft.network/virtualwans\": { \"SingularDisplayName\": \"Virtual WAN\" }\n ,\"microsoft.network/vpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Site to Site)\" }\n ,\"microsoft.network/vpngateways/vpnconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connection\" }\n ,\"microsoft.network/vpngateways/vpnconnections/vpnlinkconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connections VPN link connection\" }\n ,\"microsoft.network/vpnserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network VPN server configuration\" }\n ,\"microsoft.network/vpnsites\": { \"SingularDisplayName\": \"Microsoft.Network VPN site\" }\n ,\"microsoft.network/vpnsites/vpnsitelinks\": { \"SingularDisplayName\": \"Microsoft.Network VPN sites VPN site link\" }\n ,\"microsoft.networkanalytics/dataconnectors\": { \"SingularDisplayName\": \"AIOps - Data Connector\" }\n ,\"microsoft.networkanalytics/datalakehouses\": { \"SingularDisplayName\": \"AIOps - Data LakeHouse\" }\n ,\"microsoft.networkanalytics/dataproducts\": { \"SingularDisplayName\": \"Azure Operator Insights ? Data Product\" }\n ,\"microsoft.networkanalytics/dataproducts/datatypes\": { \"SingularDisplayName\": \"Data Type\" }\n ,\"microsoft.networkanalytics/dataproductscatalogs\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics data products catalog\" }\n ,\"microsoft.networkanalytics/metricsingestionendpoints\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics metrics ingestion endpoint\" }\n ,\"microsoft.networkanalytics/networkanalyticsproducts\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics network analytics product\" }\n ,\"microsoft.networkcloud/baremetalmachines\": { \"SingularDisplayName\": \"Bare Metal Machine (Operator Nexus)\" }\n ,\"microsoft.networkcloud/cloudservicesnetworks\": { \"SingularDisplayName\": \"Cloud Services Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clustermanagers\": { \"SingularDisplayName\": \"Cluster Manager (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters\": { \"SingularDisplayName\": \"Cluster (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/baremetalmachinekeysets\": { \"SingularDisplayName\": \"Cluster Bare Metal Machine Key Set (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/bmckeysets\": { \"SingularDisplayName\": \"Cluster Baseboard Management Controller Key Set (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/metricsconfigurations\": { \"SingularDisplayName\": \"Cluster Metrics Configuration (Operator Nexus)\" }\n ,\"microsoft.networkcloud/edgeclustermachineskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster machine SKU\" }\n ,\"microsoft.networkcloud/edgeclusterruntimeversions\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster runtime version\" }\n ,\"microsoft.networkcloud/edgeclusters\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster\" }\n ,\"microsoft.networkcloud/edgeclusters/nodes\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge clusters node\" }\n ,\"microsoft.networkcloud/edgeclusterskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster SKU\" }\n ,\"microsoft.networkcloud/kubernetesclusters\": { \"SingularDisplayName\": \"Kubernetes Cluster (Operator Nexus)\" }\n ,\"microsoft.networkcloud/kubernetesclusters/agentpools\": { \"SingularDisplayName\": \"Agent Pool (Operator Nexus)\" }\n ,\"microsoft.networkcloud/kubernetesclusters/features\": { \"SingularDisplayName\": \"Kubernetes Cluster Feature (Operator Nexus)\" }\n ,\"microsoft.networkcloud/l2networks\": { \"SingularDisplayName\": \"Layer 2 Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/l3networks\": { \"SingularDisplayName\": \"Layer 3 Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/racks\": { \"SingularDisplayName\": \"Compute Rack (Operator Nexus)\" }\n ,\"microsoft.networkcloud/rackskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud rack SKU\" }\n ,\"microsoft.networkcloud/registrationhubs\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hub\" }\n ,\"microsoft.networkcloud/registrationhubs/images\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs image\" }\n ,\"microsoft.networkcloud/registrationhubs/machines\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs machine\" }\n ,\"microsoft.networkcloud/storageappliances\": { \"SingularDisplayName\": \"Storage Appliance (Operator Nexus)\" }\n ,\"microsoft.networkcloud/trunkednetworks\": { \"SingularDisplayName\": \"Trunked Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/virtualmachines\": { \"SingularDisplayName\": \"Virtual Machine (Operator Nexus)\" }\n ,\"microsoft.networkcloud/virtualmachines/consoles\": { \"SingularDisplayName\": \"Virtual Machine Console (Operator Nexus)\" }\n ,\"microsoft.networkcloud/volumes\": { \"SingularDisplayName\": \"Volume (Operator Nexus)\" }\n ,\"microsoft.networkfunction/azuretrafficcollectors\": { \"SingularDisplayName\": \"ExpressRoute traffic collector\" }\n ,\"microsoft.networkfunction/meshvpns\": { \"SingularDisplayName\": \"Mesh VPN\" }\n ,\"microsoft.nexusidentity/identitycontrollers\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity controller\" }\n ,\"microsoft.nexusidentity/identitysets\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity set\" }\n ,\"microsoft.notebooks/notebookproxies\": { \"SingularDisplayName\": \"Microsoft.Notebooks notebook proxy\" }\n ,\"microsoft.notificationhubs/namespaces\": { \"SingularDisplayName\": \"Notification Hub Namespace\" }\n ,\"microsoft.notificationhubs/namespaces/notificationhubs\": { \"SingularDisplayName\": \"Notification Hub\" }\n ,\"microsoft.objectstore/osnamespaces\": { \"SingularDisplayName\": \"Microsoft.ObjectStore os namespace\" }\n })[tolower(id)]\n}\n", - "$fxv#3": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_4(id: string) {\n dynamic({\n \"microsoft.offazure/hypervsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv site\" }\n ,\"microsoft.offazure/hypervsites/clusters\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites cluster\" }\n ,\"microsoft.offazure/hypervsites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites host\" }\n ,\"microsoft.offazure/hypervsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites job\" }\n ,\"microsoft.offazure/hypervsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machine\" }\n ,\"microsoft.offazure/hypervsites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machines software inventory\" }\n ,\"microsoft.offazure/hypervsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites operations statu\" }\n ,\"microsoft.offazure/hypervsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites run as account\" }\n ,\"microsoft.offazure/importsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure import site\" }\n ,\"microsoft.offazure/importsites/deletejobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites delete job\" }\n ,\"microsoft.offazure/importsites/exportjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites export job\" }\n ,\"microsoft.offazure/importsites/importjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites import job\" }\n ,\"microsoft.offazure/importsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites job\" }\n ,\"microsoft.offazure/importsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites machine\" }\n ,\"microsoft.offazure/mastersites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master site\" }\n ,\"microsoft.offazure/mastersites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites operations statu\" }\n ,\"microsoft.offazure/mastersites/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private endpoint connection\" }\n ,\"microsoft.offazure/mastersites/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private link resource\" }\n ,\"microsoft.offazure/mastersites/sqlsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql site\" }\n ,\"microsoft.offazure/mastersites/sqlsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites discovery site data source\" }\n ,\"microsoft.offazure/mastersites/sqlsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites job\" }\n ,\"microsoft.offazure/mastersites/sqlsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites operations statu\" }\n ,\"microsoft.offazure/mastersites/sqlsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites run as account\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqlavailabilitygroups\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql availability group\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqldatabases\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql database\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqlservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql server\" }\n ,\"microsoft.offazure/mastersites/webappsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app site\" }\n ,\"microsoft.offazure/mastersites/webappsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites discovery site data source\" }\n ,\"microsoft.offazure/mastersites/webappsites/extendedmachines\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites extended machine\" }\n ,\"microsoft.offazure/mastersites/webappsites/iiswebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web application\" }\n ,\"microsoft.offazure/mastersites/webappsites/iiswebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web server\" }\n ,\"microsoft.offazure/mastersites/webappsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites runasaccount\" }\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web application\" }\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web server\" }\n ,\"microsoft.offazure/serversites\": { \"SingularDisplayName\": \"Microsoft.OffAzure server site\" }\n ,\"microsoft.offazure/serversites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites job\" }\n ,\"microsoft.offazure/serversites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machine\" }\n ,\"microsoft.offazure/serversites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machines software inventory\" }\n ,\"microsoft.offazure/serversites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites operations statu\" }\n ,\"microsoft.offazure/serversites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites run as account\" }\n ,\"microsoft.offazure/vmwaresites\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware site\" }\n ,\"microsoft.offazure/vmwaresites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites host\" }\n ,\"microsoft.offazure/vmwaresites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites job\" }\n ,\"microsoft.offazure/vmwaresites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machine\" }\n ,\"microsoft.offazure/vmwaresites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machines software inventory\" }\n ,\"microsoft.offazure/vmwaresites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites operations statu\" }\n ,\"microsoft.offazure/vmwaresites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites run as account\" }\n ,\"microsoft.offazure/vmwaresites/vcenters\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites vcenter\" }\n ,\"microsoft.offazurespringboot/springbootsites\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsite\" }\n ,\"microsoft.offazurespringboot/springbootsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites error summary\" }\n ,\"microsoft.offazurespringboot/springbootsites/springbootapps\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootapp\" }\n ,\"microsoft.offazurespringboot/springbootsites/springbootservers\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootserver\" }\n ,\"microsoft.offazurespringboot/springbootsites/summaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites summary\" }\n ,\"microsoft.onlineexperimentation/workspaces\": { \"SingularDisplayName\": \"Online Experimentation Workspace\" }\n ,\"microsoft.openenergyplatform/energyservices\": { \"SingularDisplayName\": \"Azure Data Manager for Energy\" }\n ,\"microsoft.openlogisticsplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspace\" }\n ,\"microsoft.openlogisticsplatform/workspaces/applicationregistrations\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application registration\" }\n ,\"microsoft.openlogisticsplatform/workspaces/applications\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application\" }\n ,\"microsoft.openlogisticsplatform/workspaces/eventgridfilters\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces event grid filter\" }\n ,\"microsoft.openlogisticsplatform/workspaces/shares\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share\" }\n ,\"microsoft.openlogisticsplatform/workspaces/sharesubscriptions\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share subscription\" }\n ,\"microsoft.operationalinsights/clusters\": { \"SingularDisplayName\": \"Log Analytics dedicated cluster\" }\n ,\"microsoft.operationalinsights/querypacks\": { \"SingularDisplayName\": \"Log Analytics query pack\" }\n ,\"microsoft.operationalinsights/workspaces\": { \"SingularDisplayName\": \"Log Analytics workspace\" }\n ,\"microsoft.operationsmanagement/managementassociations\": { \"SingularDisplayName\": \"Microsoft.OperationsManagement management association\" }\n ,\"microsoft.operationsmanagement/solutions\": { \"SingularDisplayName\": \"Solution\" }\n ,\"microsoft.operatorvoicemail/operatorvoicemailinstances\": { \"SingularDisplayName\": \"Microsoft.OperatorVoicemail operator voicemail instance\" }\n ,\"microsoft.oraclediscovery/oraclesites\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle site\" }\n ,\"microsoft.oraclediscovery/oraclesites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites error summary\" }\n ,\"microsoft.oraclediscovery/oraclesites/oracledatabases\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle database\" }\n ,\"microsoft.oraclediscovery/oraclesites/oracleservers\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle server\" }\n ,\"microsoft.oraclediscovery/oraclesites/summaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites summary\" }\n ,\"microsoft.orbital/cloudaccessrouters\": { \"SingularDisplayName\": \"Cloud Access Router\" }\n ,\"microsoft.orbital/contactprofiles\": { \"SingularDisplayName\": \"Contact Profile\" }\n ,\"microsoft.orbital/edgesites\": { \"SingularDisplayName\": \"Edge Site\" }\n ,\"microsoft.orbital/geocatalogs\": { \"SingularDisplayName\": \"GeoCatalog\" }\n ,\"microsoft.orbital/globalcommunicationssites\": { \"SingularDisplayName\": \"Microsoft.Orbital global communications site\" }\n ,\"microsoft.orbital/groundstations\": { \"SingularDisplayName\": \"Ground Station\" }\n ,\"microsoft.orbital/l2connections\": { \"SingularDisplayName\": \"L2 Connection\" }\n ,\"microsoft.orbital/sdwancontrollers\": { \"SingularDisplayName\": \"SDWAN Controller\" }\n ,\"microsoft.orbital/spacecrafts\": { \"SingularDisplayName\": \"Spacecraft\" }\n ,\"microsoft.orbital/spacecrafts/contacts\": { \"SingularDisplayName\": \"Contact\" }\n ,\"microsoft.orbital/terminals\": { \"SingularDisplayName\": \"Cloud Access Terminal\" }\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrence\" }\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences/operationresult\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrences operation result\" }\n ,\"microsoft.peering/peerasns\": { \"SingularDisplayName\": \"Microsoft.Peering peer asn\" }\n ,\"microsoft.peering/peerings\": { \"SingularDisplayName\": \"Peering\" }\n ,\"microsoft.peering/peerings/registeredasns\": { \"SingularDisplayName\": \"Registered ASN\" }\n ,\"microsoft.peering/peerings/registeredprefixes\": { \"SingularDisplayName\": \"Registered prefix\" }\n ,\"microsoft.peering/peeringservices\": { \"SingularDisplayName\": \"Peering Service\" }\n ,\"microsoft.peering/peeringservices/prefixes\": { \"SingularDisplayName\": \"Peering Service Prefix\" }\n ,\"microsoft.pki/pkis\": { \"SingularDisplayName\": \"Microsoft.Pki PKI\" }\n ,\"microsoft.pki/pkis/certificateauthorities\": { \"SingularDisplayName\": \"Microsoft.Pki pkis certificate authority\" }\n ,\"microsoft.pki/pkis/enrollmentpolicies\": { \"SingularDisplayName\": \"Microsoft.Pki pkis enrollment policy\" }\n ,\"microsoft.policyinsights/attestations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights attestation\" }\n ,\"microsoft.policyinsights/policymetadata\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights policy metadata\" }\n ,\"microsoft.policyinsights/remediations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights remediation\" }\n ,\"microsoft.portal/consoles\": { \"SingularDisplayName\": \"Microsoft.Portal console\" }\n ,\"microsoft.portal/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\n ,\"microsoft.portal/tenantconfigurations\": { \"SingularDisplayName\": \"Microsoft.Portal tenant configuration\" }\n ,\"microsoft.portal/usersettings\": { \"SingularDisplayName\": \"Microsoft.Portal user setting\" }\n ,\"microsoft.portal/virtual-privatedashboards\": { \"SingularDisplayName\": \"Private dashboard\" }\n ,\"microsoft.portalservices/copilotsettings\": { \"SingularDisplayName\": \"Microsoft.PortalServices copilot setting\" }\n ,\"microsoft.portalservices/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\n ,\"microsoft.portalservices/extensions\": { \"SingularDisplayName\": \"Portal Extension\" }\n ,\"microsoft.portalservices/extensions/deployments\": { \"SingularDisplayName\": \"Extension Deployment\" }\n ,\"microsoft.portalservices/extensions/slots\": { \"SingularDisplayName\": \"Extension Slot\" }\n ,\"microsoft.portalservices/extensions/versions\": { \"SingularDisplayName\": \"Extension Version\" }\n ,\"microsoft.portalservices/settings\": { \"SingularDisplayName\": \"Microsoft.PortalServices setting\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private endpoint connection\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private link resource\" }\n ,\"microsoft.powerbi/workspacecollections\": { \"SingularDisplayName\": \"Microsoft.PowerBI workspace collection\" }\n ,\"microsoft.powerbidedicated/autoscalevcores\": { \"SingularDisplayName\": \"Microsoft.PowerBIDedicated auto scale vcore\" }\n ,\"microsoft.powerbidedicated/capacities\": { \"SingularDisplayName\": \"Power BI Embedded\" }\n ,\"microsoft.powerplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.PowerPlatform account\" }\n ,\"microsoft.premonition/libraries\": { \"SingularDisplayName\": \"Microsoft.Premonition library\" }\n ,\"microsoft.premonition/libraries/analyses\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries analyse\" }\n ,\"microsoft.premonition/libraries/samples\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries sample\" }\n ,\"microsoft.professionalservice/resources\": { \"SingularDisplayName\": \"Professional Service\" }\n ,\"microsoft.programmableconnectivity/gateways\": { \"SingularDisplayName\": \"APC Gateway\" }\n ,\"microsoft.programmableconnectivity/operatorapiconnections\": { \"SingularDisplayName\": \"APC Operator API Connection\" }\n ,\"microsoft.programmableconnectivity/operatorapiplans\": { \"SingularDisplayName\": \"APC Operator API Plan\" }\n ,\"microsoft.proposal/proposals\": { \"SingularDisplayName\": \"Microsoft.Proposal proposal\" }\n ,\"microsoft.providerhub/providerregistrations\": { \"SingularDisplayName\": \"Resource Provider as a Service\" }\n ,\"microsoft.providerhub/providerregistrations/customrollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.providerhub/providerregistrations/defaultrollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\n ,\"microsoft.providerhubdevtest/regionalstresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest regional stresstest\" }\n ,\"microsoft.providerhubdevtest/stresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest stresstest\" }\n ,\"microsoft.purview/accounts\": { \"SingularDisplayName\": \"Microsoft Purview account\" }\n ,\"microsoft.quantum/provideraccounts\": { \"SingularDisplayName\": \"Microsoft.Quantum provider account\" }\n ,\"microsoft.quantum/workspaces\": { \"SingularDisplayName\": \"Quantum Workspace\" }\n ,\"microsoft.quota/groupquotas\": { \"SingularDisplayName\": \"Microsoft.Quota group quota\" }\n ,\"microsoft.quota/groupquotas/groupquotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas group quota request\" }\n ,\"microsoft.quota/groupquotas/quotaallocationrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation request\" }\n ,\"microsoft.quota/groupquotas/quotaallocations\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation\" }\n ,\"microsoft.quota/groupquotas/subscriptionrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription request\" }\n ,\"microsoft.quota/groupquotas/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription\" }\n ,\"microsoft.quota/quotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota quota request\" }\n ,\"microsoft.quota/quotas\": { \"SingularDisplayName\": \"Microsoft.Quota quota\" }\n ,\"microsoft.quota/usages\": { \"SingularDisplayName\": \"Microsoft.Quota usage\" }\n ,\"microsoft.recommendationsservice/accounts\": { \"SingularDisplayName\": \"Intelligent Recommendations Account\" }\n ,\"microsoft.recommendationsservice/accounts/modeling\": { \"SingularDisplayName\": \"Modeling\" }\n ,\"microsoft.recommendationsservice/accounts/serviceendpoints\": { \"SingularDisplayName\": \"Service Endpoint\" }\n ,\"microsoft.recoveryservices/replicationeligibilityresults\": { \"SingularDisplayName\": \"Microsoft.RecoveryServices replication eligibility result\" }\n ,\"microsoft.recoveryservices/vaults\": { \"SingularDisplayName\": \"Recovery Services vault\" }\n ,\"microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems\": { \"SingularDisplayName\": \"Backup Item\" }\n ,\"microsoft.recoveryservicesbvtd/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD\" }\n ,\"microsoft.recoveryservicesbvtd2/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD2\" }\n ,\"microsoft.recoveryservicesintd/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD\" }\n ,\"microsoft.recoveryservicesintd2/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD2\" }\n ,\"microsoft.redhatopenshift/openshiftclusters\": { \"SingularDisplayName\": \"Azure Red Hat OpenShift cluster\" }\n ,\"microsoft.relationships/dependencyof\": { \"SingularDisplayName\": \"Dependency Relationship\" }\n ,\"microsoft.relationships/servicegroupmember\": { \"SingularDisplayName\": \"Service group member relationship\" }\n ,\"microsoft.relationships/servicegrouprelationships\": { \"SingularDisplayName\": \"Connected Resource\" }\n ,\"microsoft.relay/namespaces\": { \"SingularDisplayName\": \"Relay\" }\n ,\"microsoft.relay/namespaces/hybridconnections\": { \"SingularDisplayName\": \"Hybrid connection\" }\n ,\"microsoft.relay/namespaces/wcfrelays\": { \"SingularDisplayName\": \"WCF relay\" }\n ,\"microsoft.resilience/resiliencestates\": { \"SingularDisplayName\": \"Microsoft.Resilience resilience state\" }\n ,\"microsoft.resourceconnector/appliances\": { \"SingularDisplayName\": \"Resource bridge\" }\n ,\"microsoft.resourcegraph/queries\": { \"SingularDisplayName\": \"Resource Graph query\" }\n ,\"microsoft.resourcehealth/availabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth availability statuse\" }\n ,\"microsoft.resourcehealth/childavailabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth child availability statuse\" }\n ,\"microsoft.resourcehealth/emergingissues\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth emerging issue\" }\n ,\"microsoft.resourcehealth/events\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth event\" }\n ,\"microsoft.resourcehealth/events/impactedresources\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth events impacted resource\" }\n ,\"microsoft.resourcehealth/metadata\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth metadata\" }\n ,\"microsoft.resources/builtintemplatespecs\": { \"SingularDisplayName\": \"Built-in template spec\" }\n ,\"microsoft.resources/changes\": { \"SingularDisplayName\": \"Microsoft.Resources change\" }\n ,\"microsoft.resources/databoundaries\": { \"SingularDisplayName\": \"Microsoft.Resources data boundary\" }\n ,\"microsoft.resources/deletedresources\": { \"SingularDisplayName\": \"Recycle Bin\" }\n ,\"microsoft.resources/deployments\": { \"SingularDisplayName\": \"Microsoft.Resources deployment\" }\n ,\"microsoft.resources/deployments/operations\": { \"SingularDisplayName\": \"Microsoft.Resources deployments operation\" }\n ,\"microsoft.resources/deploymentscripts\": { \"SingularDisplayName\": \"Deployment Script\" }\n ,\"microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\n ,\"microsoft.resources/mobobrokers\": { \"SingularDisplayName\": \"Microsoft.Resources mobo broker\" }\n ,\"microsoft.resources/resourcechange\": { \"SingularDisplayName\": \"Change Analysis\" }\n ,\"microsoft.resources/resourcechanges\": { \"SingularDisplayName\": \"Resource change\" }\n ,\"microsoft.resources/resourcegraphvisualizer\": { \"SingularDisplayName\": \"Resource Graph Visualizer\" }\n ,\"microsoft.resources/resourcegroups\": { \"SingularDisplayName\": \"Microsoft.Resources resource group\" }\n ,\"microsoft.resources/resources\": { \"SingularDisplayName\": \"Resource\" }\n ,\"microsoft.resources/snapshots\": { \"SingularDisplayName\": \"Microsoft.Resources snapshot\" }\n ,\"microsoft.resources/subscriptions\": { \"SingularDisplayName\": \"Subscription\" }\n ,\"microsoft.resources/subscriptions/resourcegroups\": { \"SingularDisplayName\": \"Resource group\" }\n ,\"microsoft.resources/tags\": { \"SingularDisplayName\": \"Microsoft.Resources tag\" }\n ,\"microsoft.resources/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\n ,\"microsoft.resources/virtualsubscriptionsforresourcepicker\": { \"SingularDisplayName\": \"Subscription\" }\n ,\"microsoft.saas/applications\": { \"SingularDisplayName\": \"Software as a Service (classic)\" }\n ,\"microsoft.saas/resources\": { \"SingularDisplayName\": \"SaaS\" }\n ,\"microsoft.saas/saasresources\": { \"SingularDisplayName\": \"SaaS (classic)\" }\n ,\"microsoft.saashub/cloudservices\": { \"SingularDisplayName\": \"Microsoft.SaaSHub cloud service\" }\n ,\"microsoft.saashub/cloudservices/hidden\": { \"SingularDisplayName\": \"Microsoft SaaS\" }\n ,\"microsoft.saashub/saasresources\": { \"SingularDisplayName\": \"Microsoft.SaaSHub saas resource\" }\n ,\"microsoft.salescopilot/conversationintelligencerecordingaccounts\": { \"SingularDisplayName\": \"Microsoft.SalesCopilot conversation intelligence recording account\" }\n ,\"microsoft.scheduler/jobcollections\": { \"SingularDisplayName\": \"Scheduler job collection\" }\n ,\"microsoft.scheduler/jobcollections/jobs\": { \"SingularDisplayName\": \"Scheduler job\" }\n ,\"microsoft.scom/managedinstances\": { \"SingularDisplayName\": \"SCOM managed instance\" }\n ,\"microsoft.scvmm/availabilitysets\": { \"SingularDisplayName\": \"Microsoft.ScVmm availability set\" }\n ,\"microsoft.scvmm/clouds\": { \"SingularDisplayName\": \"Microsoft.ScVmm cloud\" }\n ,\"microsoft.scvmm/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instance\" }\n ,\"microsoft.scvmm/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances guest agent\" }\n ,\"microsoft.scvmm/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.scvmm/virtualmachines\": { \"SingularDisplayName\": \"SCVMM virtual machine - Azure Arc\" }\n ,\"microsoft.scvmm/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine template\" }\n ,\"microsoft.scvmm/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual network\" }\n ,\"microsoft.scvmm/vmmservers\": { \"SingularDisplayName\": \"SCVMM management server\" }\n ,\"microsoft.search/searchservices\": { \"SingularDisplayName\": \"Search service\" }\n ,\"microsoft.secretmanagementsampleprovider/forecasts\": { \"SingularDisplayName\": \"Microsoft.SecretManagementSampleProvider forecast\" }\n ,\"microsoft.secretsynccontroller/azurekeyvaultsecretproviderclasses\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController Azure key vault secret provider class\" }\n ,\"microsoft.secretsynccontroller/secretsyncs\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController secret sync\" }\n ,\"microsoft.security/adaptivenetworkhardenings\": { \"SingularDisplayName\": \"Microsoft.Security adaptive network hardening\" }\n ,\"microsoft.security/advancedthreatprotectionsettings\": { \"SingularDisplayName\": \"Microsoft.Security advanced threat protection setting\" }\n ,\"microsoft.security/alertssuppressionrules\": { \"SingularDisplayName\": \"Microsoft.Security alerts suppression rule\" }\n ,\"microsoft.security/apicollections\": { \"SingularDisplayName\": \"Microsoft.Security API collection\" }\n ,\"microsoft.security/applications\": { \"SingularDisplayName\": \"Microsoft.Security application\" }\n ,\"microsoft.security/assessmentmetadata\": { \"SingularDisplayName\": \"Microsoft.Security assessment metadata\" }\n ,\"microsoft.security/assessments\": { \"SingularDisplayName\": \"Microsoft.Security assessment\" }\n ,\"microsoft.security/assessments/governanceassignments\": { \"SingularDisplayName\": \"Microsoft.Security assessments governance assignment\" }\n ,\"microsoft.security/assessments/subassessments\": { \"SingularDisplayName\": \"Microsoft.Security assessments sub assessment\" }\n ,\"microsoft.security/assignments\": { \"SingularDisplayName\": \"Microsoft.Security assignment\" }\n ,\"microsoft.security/automations\": { \"SingularDisplayName\": \"Microsoft.Security automation\" }\n ,\"microsoft.security/autoprovisioningsettings\": { \"SingularDisplayName\": \"Microsoft.Security auto provisioning setting\" }\n ,\"microsoft.security/complianceresults\": { \"SingularDisplayName\": \"Microsoft.Security compliance result\" }\n ,\"microsoft.security/compliances\": { \"SingularDisplayName\": \"Microsoft.Security compliance\" }\n ,\"microsoft.security/connectors\": { \"SingularDisplayName\": \"Microsoft.Security connector\" }\n ,\"microsoft.security/customassessmentautomations\": { \"SingularDisplayName\": \"Microsoft.Security custom assessment automation\" }\n ,\"microsoft.security/defenderforstoragesettings\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage setting\" }\n ,\"microsoft.security/defenderforstoragesettings/malwarescans\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage settings malware scan\" }\n ,\"microsoft.security/devicesecuritygroups\": { \"SingularDisplayName\": \"Microsoft.Security device security group\" }\n ,\"microsoft.security/governancerules\": { \"SingularDisplayName\": \"Microsoft.Security governance rule\" }\n ,\"microsoft.security/governancerules/operationresults\": { \"SingularDisplayName\": \"Microsoft.Security governance rules operation result\" }\n ,\"microsoft.security/healthreports\": { \"SingularDisplayName\": \"Microsoft.Security health report\" }\n ,\"microsoft.security/informationprotectionpolicies\": { \"SingularDisplayName\": \"Microsoft.Security information protection policy\" }\n ,\"microsoft.security/iotsecuritysolutions\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solution\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics model\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated alert\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated recommendation\" }\n ,\"microsoft.security/iotsecuritysolutions/iotalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert\" }\n ,\"microsoft.security/iotsecuritysolutions/iotalerttypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert type\" }\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation\" }\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendationtypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation type\" }\n ,\"microsoft.security/locations/alerts\": { \"SingularDisplayName\": \"Security Alert\" }\n ,\"microsoft.security/mdeonboardings\": { \"SingularDisplayName\": \"Microsoft.Security mde onboarding\" }\n ,\"microsoft.security/pricings\": { \"SingularDisplayName\": \"Defender for Cloud\" }\n ,\"microsoft.security/pricings/securityoperators\": { \"SingularDisplayName\": \"Microsoft.Security pricings security operator\" }\n ,\"microsoft.security/regulatorycompliancestandards\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standard\" }\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance control\" }\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols/regulatorycomplianceassessments\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance controls regulatory compliance assessment\" }\n ,\"microsoft.security/securescores\": { \"SingularDisplayName\": \"Microsoft.Security secure score\" }\n ,\"microsoft.security/securityconnectors\": { \"SingularDisplayName\": \"Microsoft.Security security connector\" }\n ,\"microsoft.security/securityconnectors/devops\": { \"SingularDisplayName\": \"Microsoft.Security security connectors devop\" }\n ,\"microsoft.security/securitycontacts\": { \"SingularDisplayName\": \"Microsoft.Security security contact\" }\n ,\"microsoft.security/sensitivitysettings\": { \"SingularDisplayName\": \"Microsoft.Security sensitivity setting\" }\n ,\"microsoft.security/servervulnerabilityassessments\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessment\" }\n ,\"microsoft.security/servervulnerabilityassessmentssettings\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessments setting\" }\n ,\"microsoft.security/settings\": { \"SingularDisplayName\": \"Microsoft.Security setting\" }\n ,\"microsoft.security/standards\": { \"SingularDisplayName\": \"Microsoft.Security standard\" }\n ,\"microsoft.security/workspacesettings\": { \"SingularDisplayName\": \"Microsoft.Security workspace setting\" }\n ,\"microsoft.securitycopilot/capacities\": { \"SingularDisplayName\": \"Microsoft Security compute capacity\" }\n ,\"microsoft.securitydetonation/chambers\": { \"SingularDisplayName\": \"Security Detonation Chamber\" }\n ,\"microsoft.securityinsightsarg/sentinel\": { \"SingularDisplayName\": \"Microsoft Sentinel\" }\n ,\"microsoft.sentinelplatformservices/sentinelplatformservices\": { \"SingularDisplayName\": \"Microsoft.SentinelPlatformServices sentinel platform service\" }\n ,\"microsoft.serialconsole/consoleservices\": { \"SingularDisplayName\": \"Microsoft.SerialConsole console service\" }\n ,\"microsoft.serialconsole/serialports\": { \"SingularDisplayName\": \"Microsoft.SerialConsole serial port\" }\n ,\"microsoft.servicebus/namespaces\": { \"SingularDisplayName\": \"Service Bus namespace\" }\n ,\"microsoft.servicebus/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Service Bus Geo-DR Alias\" }\n ,\"microsoft.servicebus/namespaces/queues\": { \"SingularDisplayName\": \"Service Bus queue\" }\n ,\"microsoft.servicebus/namespaces/topics\": { \"SingularDisplayName\": \"Service Bus topic\" }\n ,\"microsoft.servicebus/namespaces/topics/subscriptions\": { \"SingularDisplayName\": \"Service Bus Subscription\" }\n ,\"microsoft.servicefabric/clusters\": { \"SingularDisplayName\": \"Service Fabric cluster\" }\n ,\"microsoft.servicefabric/managedclusters\": { \"SingularDisplayName\": \"Service Fabric managed cluster\" }\n ,\"microsoft.servicefabricmesh/applications\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh application\" }\n ,\"microsoft.servicefabricmesh/applications/services\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications service\" }\n ,\"microsoft.servicefabricmesh/applications/services/replicas\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications services replica\" }\n ,\"microsoft.servicefabricmesh/gateways\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh gateway\" }\n ,\"microsoft.servicefabricmesh/networks\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh network\" }\n ,\"microsoft.servicefabricmesh/secrets\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secret\" }\n ,\"microsoft.servicefabricmesh/secrets/values\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secrets value\" }\n ,\"microsoft.servicefabricmesh/volumes\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh volume\" }\n ,\"microsoft.servicelinker/dryruns\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker dryrun\" }\n ,\"microsoft.servicelinker/linkers\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker linker\" }\n ,\"microsoft.servicenetworking/trafficcontrollers\": { \"SingularDisplayName\": \"Application Gateway for Containers\" }\n ,\"microsoft.serviceshub/connectors\": { \"SingularDisplayName\": \"Services Hub Connector\" }\n ,\"microsoft.signalrservice/signalr\": { \"SingularDisplayName\": \"SignalR\" }\n ,\"microsoft.signalrservice/signalr/replicas\": { \"SingularDisplayName\": \"SignalR Replica\" }\n ,\"microsoft.signalrservice/webpubsub\": { \"SingularDisplayName\": \"Web PubSub Service\" }\n ,\"microsoft.signalrservice/webpubsub/replicas\": { \"SingularDisplayName\": \"Web PubSub Service Replica\" }\n ,\"microsoft.skytap/billingnodes\": { \"SingularDisplayName\": \"Microsoft.Skytap billing node\" }\n ,\"microsoft.skytap/interfaces\": { \"SingularDisplayName\": \"Microsoft.Skytap interface\" }\n ,\"microsoft.skytap/nodes\": { \"SingularDisplayName\": \"Microsoft.Skytap node\" }\n ,\"microsoft.softwareplan/hybridusebenefits\": { \"SingularDisplayName\": \"Microsoft.SoftwarePlan hybrid use benefit\" }\n ,\"microsoft.solutions/applicationdefinitions\": { \"SingularDisplayName\": \"Service catalog managed application definition\" }\n ,\"microsoft.solutions/applications\": { \"SingularDisplayName\": \"Managed application\" }\n ,\"microsoft.solutions/jitrequests\": { \"SingularDisplayName\": \"Microsoft.Solutions JIT request\" }\n ,\"microsoft.sovereign/landingzoneaccounts\": { \"SingularDisplayName\": \"Landing zone account\" }\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\n ,\"microsoft.sovereign/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\n ,\"microsoft.sovereign/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\n ,\"microsoft.sovereign/transparencylogs\": { \"SingularDisplayName\": \"Transparency log\" }\n ,\"microsoft.sql/azuresql\": { \"SingularDisplayName\": \"Azure SQL resource\" }\n ,\"microsoft.sql/instancepools\": { \"SingularDisplayName\": \"Instance pool\" }\n ,\"microsoft.sql/managedinstances\": { \"SingularDisplayName\": \"SQL managed instance\" }\n ,\"microsoft.sql/managedinstances/databases\": { \"SingularDisplayName\": \"Managed database\" }\n ,\"microsoft.sql/servers\": { \"SingularDisplayName\": \"SQL server\" }\n ,\"microsoft.sql/servers/databases\": { \"SingularDisplayName\": \"SQL database\" }\n ,\"microsoft.sql/servers/elasticpools\": { \"SingularDisplayName\": \"SQL elastic pool\" }\n ,\"microsoft.sql/servers/jobagents\": { \"SingularDisplayName\": \"Elastic Job agent\" }\n ,\"microsoft.sql/virtualclusters\": { \"SingularDisplayName\": \"Virtual cluster\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine group\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups/availabilitygrouplisteners\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine groups availability group listener\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachines\": { \"SingularDisplayName\": \"SQL virtual machine\" }\n ,\"microsoft.standbypool/standbycontainergrouppools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pool\" }\n ,\"microsoft.standbypool/standbycontainergrouppools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pools runtime view\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pool\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools runtime view\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools/standbyvirtualmachines\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools standby virtual machine\" }\n ,\"microsoft.storage/storageaccounts\": { \"SingularDisplayName\": \"Storage account\" }\n ,\"microsoft.storageactions/storagetasks\": { \"SingularDisplayName\": \"Storage task - Azure Storage Actions\" }\n ,\"microsoft.storagecache/amlfilesystems\": { \"SingularDisplayName\": \"Azure Managed Lustre\" }\n ,\"microsoft.storagecache/caches\": { \"SingularDisplayName\": \"HPC cache\" }\n ,\"microsoft.storagediscovery/storagediscoveryworkspaces\": { \"SingularDisplayName\": \"Storage Discovery workspace\" }\n ,\"microsoft.storagehub/all\": { \"SingularDisplayName\": \"All resources\" }\n ,\"microsoft.storagehub/policycomplianceresources\": { \"SingularDisplayName\": \"Policy compliance\" }\n ,\"microsoft.storageinsights/storagecollectionrules\": { \"SingularDisplayName\": \"Microsoft.StorageInsights storage collection rule\" }\n ,\"microsoft.storagemover/storagemovers\": { \"SingularDisplayName\": \"Storage mover\" }\n ,\"microsoft.storagepool/diskpools\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pool\" }\n ,\"microsoft.storagepool/diskpools/iscsitargets\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pools iscsi target\" }\n ,\"microsoft.storagesync/storagesyncservices\": { \"SingularDisplayName\": \"Storage Sync Service\" }\n ,\"microsoft.storagetasks/storagetasks\": { \"SingularDisplayName\": \"Microsoft.StorageTasks storage task\" }\n ,\"microsoft.storsimple/managers\": { \"SingularDisplayName\": \"StorSimple device manager\" }\n ,\"microsoft.storsimple/managers/accesscontrolrecords\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers access control record\" }\n ,\"microsoft.storsimple/managers/bandwidthsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers bandwidth setting\" }\n ,\"microsoft.storsimple/managers/certificates\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers certificate\" }\n ,\"microsoft.storsimple/managers/devices\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers device\" }\n ,\"microsoft.storsimple/managers/devices/alertsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices alert setting\" }\n ,\"microsoft.storsimple/managers/devices/backuppolicies\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policy\" }\n ,\"microsoft.storsimple/managers/devices/backuppolicies/schedules\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policies schedule\" }\n ,\"microsoft.storsimple/managers/devices/backupschedulegroups\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup schedule group\" }\n ,\"microsoft.storsimple/managers/devices/chapsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices chap setting\" }\n ,\"microsoft.storsimple/managers/devices/fileservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileserver\" }\n ,\"microsoft.storsimple/managers/devices/fileservers/shares\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileservers share\" }\n ,\"microsoft.storsimple/managers/devices/iscsiservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiserver\" }\n ,\"microsoft.storsimple/managers/devices/iscsiservers/disks\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiservers disk\" }\n ,\"microsoft.storsimple/managers/devices/jobs\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices job\" }\n ,\"microsoft.storsimple/managers/devices/networksettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices network setting\" }\n ,\"microsoft.storsimple/managers/devices/securitysettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices security setting\" }\n ,\"microsoft.storsimple/managers/devices/timesettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices time setting\" }\n ,\"microsoft.storsimple/managers/devices/updatesummary\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices update summary\" }\n ,\"microsoft.storsimple/managers/devices/volumecontainers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume container\" }\n ,\"microsoft.storsimple/managers/devices/volumecontainers/volumes\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume containers volume\" }\n ,\"microsoft.storsimple/managers/encryptionsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers encryption setting\" }\n ,\"microsoft.storsimple/managers/extendedinformation\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers extended information\" }\n ,\"microsoft.storsimple/managers/storageaccountcredentials\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage account credential\" }\n ,\"microsoft.storsimple/managers/storagedomains\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage domain\" }\n ,\"microsoft.streamanalytics/clusters\": { \"SingularDisplayName\": \"Stream Analytics cluster\" }\n ,\"microsoft.streamanalytics/streamingjobs\": { \"SingularDisplayName\": \"Stream Analytics job\" }\n ,\"microsoft.subscription/aliases\": { \"SingularDisplayName\": \"Microsoft.Subscription aliase\" }\n ,\"microsoft.subscription/changetenantrequest\": { \"SingularDisplayName\": \"Microsoft.Subscription change tenant request\" }\n ,\"microsoft.subscription/policies\": { \"SingularDisplayName\": \"Microsoft.Subscription policy\" }\n ,\"microsoft.subscription/subscriptiondefinitions\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription definition\" }\n ,\"microsoft.subscription/subscriptionoperations\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription operation\" }\n ,\"microsoft.support/fileworkspaces\": { \"SingularDisplayName\": \"Microsoft.Support file workspace\" }\n ,\"microsoft.support/fileworkspaces/files\": { \"SingularDisplayName\": \"Microsoft.Support file workspaces file\" }\n ,\"microsoft.support/services\": { \"SingularDisplayName\": \"Microsoft.Support service\" }\n ,\"microsoft.support/services/problemclassifications\": { \"SingularDisplayName\": \"Microsoft.Support services problem classification\" }\n ,\"microsoft.support/supporttickets\": { \"SingularDisplayName\": \"Support Request\" }\n ,\"microsoft.sustainabilityservices/calculations\": { \"SingularDisplayName\": \"Project Sustainability Calculator\" }\n ,\"microsoft.symphony/instances\": { \"SingularDisplayName\": \"Microsoft.Symphony instance\" }\n ,\"microsoft.symphony/solutions\": { \"SingularDisplayName\": \"Microsoft.Symphony solution\" }\n ,\"microsoft.symphony/targets\": { \"SingularDisplayName\": \"Microsoft.Symphony target\" }\n ,\"microsoft.synapse/privatelinkhubs\": { \"SingularDisplayName\": \"Synapse private link hub\" }\n ,\"microsoft.synapse/workspaces\": { \"SingularDisplayName\": \"Synapse workspace\" }\n ,\"microsoft.synapse/workspaces/bigdatapools\": { \"SingularDisplayName\": \"Apache Spark pool\" }\n ,\"microsoft.synapse/workspaces/kustopools\": { \"SingularDisplayName\": \"Data Explorer pool\" }\n ,\"microsoft.synapse/workspaces/kustopools/databases\": { \"SingularDisplayName\": \"Data Explorer Database\" }\n ,\"microsoft.synapse/workspaces/scopepools\": { \"SingularDisplayName\": \"SCOPE pool\" }\n ,\"microsoft.synapse/workspaces/sqlpools\": { \"SingularDisplayName\": \"Dedicated SQL pool\" }\n ,\"microsoft.syntex/accounts\": { \"SingularDisplayName\": \"Microsoft.Syntex account\" }\n ,\"microsoft.syntex/documentprocessors\": { \"SingularDisplayName\": \"Microsoft.Syntex document processor\" }\n ,\"microsoft.test/healthdataaiservices\": { \"SingularDisplayName\": \"Azure Health Data and AI Services\" }\n ,\"microsoft.timeseriesinsights/environments\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environment\" }\n ,\"microsoft.timeseriesinsights/environments/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments access policy\" }\n ,\"microsoft.timeseriesinsights/environments/eventsources\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments event source\" }\n ,\"microsoft.timeseriesinsights/environments/referencedatasets\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments reference data set\" }\n ,\"microsoft.toolchainorchestrator/activations\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator activation\" }\n ,\"microsoft.toolchainorchestrator/campaigns\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaign\" }\n ,\"microsoft.toolchainorchestrator/campaigns/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaigns version\" }\n ,\"microsoft.toolchainorchestrator/catalogs\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalog\" }\n ,\"microsoft.toolchainorchestrator/catalogs/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalogs version\" }\n ,\"microsoft.toolchainorchestrator/diagnostics\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator diagnostic\" }\n ,\"microsoft.toolchainorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instance\" }\n ,\"microsoft.toolchainorchestrator/instances/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instances version\" }\n ,\"microsoft.toolchainorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solution\" }\n ,\"microsoft.toolchainorchestrator/solutions/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solutions version\" }\n ,\"microsoft.toolchainorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator target\" }\n ,\"microsoft.toolchainorchestrator/targets/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator targets version\" }\n ,\"microsoft.updatemanager/updaterules\": { \"SingularDisplayName\": \"Update Rule\" }\n ,\"microsoft.usagebilling/accounts\": { \"SingularDisplayName\": \"Microsoft.UsageBilling account\" }\n ,\"microsoft.usagebilling/accounts/dataexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts data export\" }\n ,\"microsoft.usagebilling/accounts/inputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts input\" }\n ,\"microsoft.usagebilling/accounts/metricexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts metric export\" }\n ,\"microsoft.usagebilling/accounts/pav2outputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pav2output\" }\n ,\"microsoft.usagebilling/accounts/pipelines\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipeline\" }\n ,\"microsoft.usagebilling/accounts/pipelines/outputselectors\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipelines output selector\" }\n ,\"microsoft.verifiedid/authorities\": { \"SingularDisplayName\": \"Microsoft.VerifiedId authority\" }\n ,\"microsoft.videoindexer/accounts\": { \"SingularDisplayName\": \"Azure AI Video Indexer\" }\n ,\"microsoft.virtualmachineimages/imagetemplates\": { \"SingularDisplayName\": \"Image template\" }\n ,\"microsoft.visualstudio/account\": { \"SingularDisplayName\": \"Azure DevOps organization\" }\n ,\"microsoft.vmware/resourcepools\": { \"SingularDisplayName\": \"Microsoft.VMware resource pool\" }\n ,\"microsoft.vmware/vcenters\": { \"SingularDisplayName\": \"Microsoft.VMware vcenter\" }\n ,\"microsoft.vmware/vcenters/inventoryitems\": { \"SingularDisplayName\": \"Microsoft.VMware vcenters inventory item\" }\n ,\"microsoft.vmware/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine\" }\n ,\"microsoft.vmware/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine template\" }\n ,\"microsoft.vmware/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.VMware virtual network\" }\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudnodes\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud node\" }\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudservices\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud service\" }\n ,\"microsoft.vmwarecloudsimple/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple virtual machine\" }\n ,\"microsoft.vnfmanager/devices\": { \"SingularDisplayName\": \"Microsoft.VnfManager device\" }\n ,\"microsoft.vnfmanager/vendors\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendor\" }\n ,\"microsoft.vnfmanager/vendors/skus\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendors SKU\" }\n ,\"microsoft.vnfmanager/vnfs\": { \"SingularDisplayName\": \"Microsoft.VnfManager vnf\" }\n ,\"microsoft.voiceservices/communicationsgateways\": { \"SingularDisplayName\": \"Communications Gateway\" }\n ,\"microsoft.voiceservices/communicationsgateways/testlines\": { \"SingularDisplayName\": \"Communications Gateway Test Line\" }\n ,\"microsoft.vsonline/accounts\": { \"SingularDisplayName\": \"Microsoft.VSOnline account\" }\n ,\"microsoft.vsonline/plans\": { \"SingularDisplayName\": \"Visual Studio Online Plan\" }\n ,\"microsoft.web/certificates\": { \"SingularDisplayName\": \"Microsoft.Web certificate\" }\n ,\"microsoft.web/connectiongateways\": { \"SingularDisplayName\": \"App Service on-premises data gateway\" }\n ,\"microsoft.web/connections\": { \"SingularDisplayName\": \"App Service API connection\" }\n ,\"microsoft.web/containerapps\": { \"SingularDisplayName\": \"Microsoft.Web container app\" }\n ,\"microsoft.web/containerapps/revisions\": { \"SingularDisplayName\": \"Microsoft.Web container apps revision\" }\n ,\"microsoft.web/customapis\": { \"SingularDisplayName\": \"Logic apps custom connector\" }\n ,\"microsoft.web/deletedsites\": { \"SingularDisplayName\": \"Microsoft.Web deleted site\" }\n ,\"microsoft.web/hostingenvironments\": { \"SingularDisplayName\": \"App Service Environment\" }\n ,\"microsoft.web/ishostingenvironmentnameavailable\": { \"SingularDisplayName\": \"Microsoft.Web ishostingenvironmentnameavailable\" }\n ,\"microsoft.web/kubeenvironments\": { \"SingularDisplayName\": \"App Service Kubernetes Environment\" }\n ,\"microsoft.web/logicappstemplate\": { \"SingularDisplayName\": \"Logic Apps Template\" }\n ,\"microsoft.web/publishingusers\": { \"SingularDisplayName\": \"Microsoft.Web publishing user\" }\n ,\"microsoft.web/serverfarms\": { \"SingularDisplayName\": \"App Service plan\" }\n ,\"microsoft.web/sites\": { \"SingularDisplayName\": \"App Service web app\" }\n ,\"microsoft.web/sites/slots\": { \"SingularDisplayName\": \"App Service deployment slot\" }\n ,\"microsoft.web/sourcecontrols\": { \"SingularDisplayName\": \"Microsoft.Web sourcecontrol\" }\n ,\"microsoft.web/staticsites\": { \"SingularDisplayName\": \"Static Web App\" }\n ,\"microsoft.weightsandbiases/instances\": { \"SingularDisplayName\": \"Azure Native Weights & Biases Cloud Service\" }\n ,\"microsoft.whiteboxcadlprovider/whiteboxresources\": { \"SingularDisplayName\": \"Microsoft.WhiteBoxCadlProvider white box resource\" }\n ,\"microsoft.windows365/cloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.Windows365 cloud pc delegated msi\" }\n ,\"microsoft.windowsesu/multipleactivationkeys\": { \"SingularDisplayName\": \"Microsoft.WindowsESU multiple activation key\" }\n ,\"microsoft.windowsiot/deviceservices\": { \"SingularDisplayName\": \"Microsoft.WindowsIoT device service\" }\n ,\"microsoft.windowspushnotificationservices/registrations\": { \"SingularDisplayName\": \"Windows Push Notification Service\" }\n ,\"microsoft.workloadmonitor/monitors\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitor\" }\n ,\"microsoft.workloadmonitor/monitors/history\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitors history\" }\n ,\"microsoft.workloads/configurationvalidationresults\": { \"SingularDisplayName\": \"Microsoft.Workloads configuration validation result\" }\n ,\"microsoft.workloads/connectors\": { \"SingularDisplayName\": \"Microsoft.Workloads connector\" }\n ,\"microsoft.workloads/connectors/acssbackups\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors acss backup\" }\n ,\"microsoft.workloads/connectors/amsinsights\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors ams insight\" }\n ,\"microsoft.workloads/connectors/sapvirtualinstancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors sap virtual instance monitor\" }\n ,\"microsoft.workloads/epicvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for Epic solution\" }\n ,\"microsoft.workloads/insights\": { \"SingularDisplayName\": \"Microsoft.Workloads insight\" }\n ,\"microsoft.workloads/instancegroupmonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance group monitor\" }\n ,\"microsoft.workloads/instancehealthdefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definition\" }\n ,\"microsoft.workloads/instancehealthdefinitions/signaldefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definitions signal definition\" }\n ,\"microsoft.workloads/instancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance monitor\" }\n ,\"microsoft.workloads/monitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP solutions\" }\n ,\"microsoft.workloads/oraclevirtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instance\" }\n ,\"microsoft.workloads/oraclevirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instances database instance\" }\n ,\"microsoft.workloads/phpworkloads\": { \"SingularDisplayName\": \"Microsoft.Workloads php workload\" }\n ,\"microsoft.workloads/phpworkloads/wordpressinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads php workloads wordpress instance\" }\n ,\"microsoft.workloads/sapdiscoverysites\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery site\" }\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instance\" }\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances/serverinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instances server instance\" }\n ,\"microsoft.workloads/sapvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/applicationinstances\": { \"SingularDisplayName\": \"App server instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/centralinstances\": { \"SingularDisplayName\": \"Central service instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Database for SAP solutions\" }\n ,\"microsoft.workloads/virtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instance\" }\n ,\"microsoft.workloads/virtualinstances/components\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instances component\" }\n ,\"microsoft.workloads/workloadinstance\": { \"SingularDisplayName\": \"My Resource\" }\n ,\"microsoft.zerotrustsegmentation/segmentationmanagers\": { \"SingularDisplayName\": \"Segmentation Manager\" }\n ,\"mongodb.atlas/organizations\": { \"SingularDisplayName\": \"MongoDB Atlas Organization\" }\n ,\"neon.postgres/organizations\": { \"SingularDisplayName\": \"Neon Serverless Postgres Organization\" }\n ,\"newrelic.observability/monitors\": { \"SingularDisplayName\": \"New Relic\" }\n ,\"nginx.nginxplus/nginxdeployments\": { \"SingularDisplayName\": \"NGINXaaS\" }\n ,\"oracle.database/autonomousdatabases\": { \"SingularDisplayName\": \"Autonomous Database\" }\n ,\"oracle.database/basedb\": { \"SingularDisplayName\": \"Autonomous Database\" }\n ,\"oracle.database/cloudexadatainfrastructures\": { \"SingularDisplayName\": \"Oracle Exadata Infrastructure\" }\n ,\"oracle.database/cloudvmclusters\": { \"SingularDisplayName\": \"Oracle Exadata VM Cluster\" }\n ,\"oracle.database/exadbvmclusters\": { \"SingularDisplayName\": \"Oracle Exascale VM Cluster\" }\n ,\"oracle.database/exascaledbstoragevaults\": { \"SingularDisplayName\": \"Oracle Exascale DB Storage Vault\" }\n ,\"oracle.database/networkanchors\": { \"SingularDisplayName\": \"Network Anchor\" }\n ,\"oracle.database/oraclesubscriptions\": { \"SingularDisplayName\": \"OracleSubscription\" }\n ,\"oracle.database/resourceanchors\": { \"SingularDisplayName\": \"Resource Anchor\" }\n ,\"paloaltonetworks.cloudngfw/firewalls\": { \"SingularDisplayName\": \"Cloud NGFW by Palo Alto Networks\" }\n ,\"paloaltonetworks.cloudngfw/globalrulestacks\": { \"SingularDisplayName\": \"Global Rulestack\" }\n ,\"paloaltonetworks.cloudngfw/localrulestacks\": { \"SingularDisplayName\": \"Local Rulestack for Cloud NGFW by Palo Alto Networks\" }\n ,\"pinecone.vectordb/organizations\": { \"SingularDisplayName\": \"Azure Native Pinecone Cloud Service\" }\n ,\"purestorage.block/reservations\": { \"SingularDisplayName\": \"Azure Native Pure Storage Cloud Service\" }\n ,\"purestorage.block/storagepools\": { \"SingularDisplayName\": \"Storage pool\" }\n ,\"purestorage.block/storagepools/avsstoragecontainers\": { \"SingularDisplayName\": \"PureStorage.Block storage pools avs storage container\" }\n })[tolower(id)]\n}\n", - "$fxv#4": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_5(id: string) {\n dynamic({\n \"qumulo.qaas/storages\": { \"SingularDisplayName\": \"Qumulo.QaaS storage\" }\n ,\"qumulo.storage/filesystems\": { \"SingularDisplayName\": \"Azure Native Qumulo Scalable File Service\" }\n ,\"solarwinds.observability/organizations\": { \"SingularDisplayName\": \"SolarWinds Observability\" }\n ,\"splitio.experimentation/experimentationworkspaces\": { \"SingularDisplayName\": \"Split Experimentation Workspace\" }\n ,\"wandisco.fusion/migrators\": { \"SingularDisplayName\": \"LiveData Migrator\" }\n ,\"wandisco.fusion/migrators/datatransferagents\": { \"SingularDisplayName\": \"Data Transfer Agent\" }\n ,\"wandisco.fusion/migrators/exclusiontemplates\": { \"SingularDisplayName\": \"Exclusion\" }\n ,\"wandisco.fusion/migrators/livedatamigrations\": { \"SingularDisplayName\": \"Migration\" }\n ,\"wandisco.fusion/migrators/metadatamigrations\": { \"SingularDisplayName\": \"Metadata Migration\" }\n ,\"wandisco.fusion/migrators/metadatatargets\": { \"SingularDisplayName\": \"Metadata Target\" }\n ,\"wandisco.fusion/migrators/pathmappings\": { \"SingularDisplayName\": \"Path Mapping\" }\n ,\"wandisco.fusion/migrators/targets\": { \"SingularDisplayName\": \"Target\" }\n ,\"wandisco.fusion/migrators/verifications\": { \"SingularDisplayName\": \"Verification\" }\n })[tolower(id)]\n}\n", - "$fxv#5": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// resource_type\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData')\nresource_type(id: string) {\n coalesce(_resource_type_1(id), _resource_type_2(id), _resource_type_3(id), _resource_type_4(id), _resource_type_5(id))\n}\n", - "$fxv#6": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Common utility functions\n//\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\n//======================================================================================================================\n\n\n//===| Date functions |=================================================================================================\n\n// monthstring\n.create-or-alter function \nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \nmonthstring(['date']: datetime, length: int = 9)\n{\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\n}\n\n// datestring\n.create-or-alter function \nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n let month = (d: datetime) { monthstring(d, 3) };\n let endDate = iff(end == datetime('0001-01-01'), start, end);\n let sameDate = startofday(start) == startofday(endDate);\n let sameMonth = startofmonth(start) == startofmonth(endDate);\n let sameYear = startofyear(start) == startofyear(endDate);\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\n let currentYear = sameYear and startofyear(start) == startofyear(now());\n case(\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\n fullYear,\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\n // 1 full mo, same year | Mmm yyyy\n fullMonth and sameMonth and sameYear,\n strcat(month(start), ' ', getyear(start)),\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\n fullMonth and sameYear,\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\n fullMonth and not(sameYear),\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\n sameDate,\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\n not(fullMonth) and sameMonth and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\n not(fullMonth) and not(sameMonth) and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\n )\n}\n\n// daterange\n.create-or-alter function \nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n datestring(start, end)\n}\n\n// monthsago\n.create-or-alter function \nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\nmonthsago(months: int)\n{\n datetime_add('month', -months, startofmonth(now()))\n}\n\n\n//===| Number functions |===============================================================================================\n// NOTE: Must be defined before string converters\n\n// delta\n.create-or-alter function \nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \ndelta(oldval: double, newval: double)\n{\n (newval - todouble(oldval))/oldval\n}\n\n// percentOfTotal\n// NOTE: Must be before percent() function\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercentOfTotal(t: (Count: long), tot: long)\n{\n let total = todouble(tot);\n t \n | extend Percent = round(Count / total * 100, 3) \n | order by Count desc\n}\n\n// percent\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercent(t: (Count: long))\n{\n let total = todouble(toscalar(t | summarize sum(Count)));\n percentOfTotal(t, total)\n}\n\n// plusminus\n.create-or-alter function \nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\nplusminus(val: string)\n{\n let neg = substring(val, 0, 1) == '-';\n iff(neg, val, strcat('+', val))\n}\n\n// updown\n.create-or-alter function \nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\nupdown(val: string)\n{\n // TODO: Handle 0\n let neg = substring(val, 0, 1) == '-';\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\n}\n\n\n//===| String functions |===============================================================================================\n\n// percentstring\n// NOTE: Must be defined before deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\npercentstring(num: double, total: double = 1.0, places: int = 9)\n{\n let value = 1.0 * num / total * 100;\n strcat(case(\n places != 9, round(value, places),\n value < 10, round(value, 2),\n round(value, 1)\n ), '%')\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// arraystring\n.create-or-alter function \nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\narraystring(arr: dynamic)\n{\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\n tostring(arr)\n , @'^\\[\"', '')\n , @'\"\\]$', '')\n , @'^, ', '')\n , @', $', '')\n , @'^\\[]$', '')\n , '\",\"', ', ')\n}\n\n// deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\n{\n let d = delta(oldval, newval);\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\n}\n\n// diffstring\n.create-or-alter function \nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\ndiffstring(oldval: double, newval: double, places: int = 1)\n{\n plusminus(round(newval - oldval, places))\n}\n\n// numberstring\n.create-or-alter function \nwith (docstring = 'Convert a number to a string', folder = 'Common')\nnumberstring(num: double, abbrev: bool = true)\n{\n replace_regex(case(\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\n tostring(num)\n ), @'\\.0$', '')\n}\n\n\n//===| Other |==========================================================================================================\n\n// ifempty\n.create-or-alter function \nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\nifempty(val: dynamic, defaultVal: dynamic)\n{\n iff(isempty(val), defaultVal, val)\n}\n", - "$fxv#7": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Settings |=======================================================================================================\n\n.create-merge table HubSettingsLog (\n version: string,\n scopes: dynamic,\n retention: dynamic\n)\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// HubSettings function\n.create-or-alter function\nwith (docstring='Gets the latest version of hub settings.', folder='Settings')\nHubSettings()\n{\n HubSettingsLog\n | extend timestamp = ingestion_time()\n | summarize arg_max(timestamp, *)\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// HubScopes function\n.create-or-alter function\nwith (docstring='Gets the currently configured scopes.', folder='Settings')\nHubScopes()\n{\n HubSettings\n | project scopes\n | mv-expand scopes\n}\n\n\n//===| Open data |======================================================================================================\n\n// PricingUnits -- Create table if it doesn't exist\n.create-merge table PricingUnits ( ignore: string )\n\n// PricingUnits -- Remove all columns\n.alter table PricingUnits ( ignore: string )\n\n// PricingUnits -- Redefine all columns to change types\n.alter table PricingUnits (\n x_PricingUnitDescription: string,\n x_PricingBlockSize: real,\n PricingUnit: string\n)\n\n// Regions\n.create-merge table Regions(\n ResourceLocation: string,\n RegionId: string,\n RegionName: string\n)\n\n// ResourceTypes\n.create-merge table ResourceTypes(\n x_ResourceType: string,\n SingularDisplayName: string,\n PluralDisplayName: string,\n LowerSingularDisplayName: string,\n LowerPluralDisplayName: string,\n IsPreview: bool,\n Description: string,\n IconUri: string\n)\n\n// Services\n.create-merge table Services(\n x_ConsumedService: string,\n x_ResourceType: string,\n ServiceName: string,\n ServiceCategory: string,\n ServiceSubcategory: string,\n PublisherName: string,\n x_PublisherCategory: string,\n x_Environment: string,\n x_ServiceModel: string\n)\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// parse_resourceid\n.create-or-alter function\nwith (docstring = 'Parses an Azure resource ID to extract resource attributes like the name, type, resource group, and subaccount ID.', folder = 'Common')\nparse_resourceid(resourceId: string) {\n let ResourceId = tolower(resourceId);\n // let ResourceId = tolower('/providers/Microsoft.BillingBenefits/savingsPlanOrders/2d2e284b-0638-427e-b8c6-1b874d4f17c8/sp/xxx');\n let SubAccountId = tostring(extract('/subscriptions/[^/]+', 1, ResourceId));\n let x_ResourceGroupName = tostring(extract('/resourcegroups/[^/]+', 1, ResourceId));\n let providerPath = iff(ResourceId !contains '/providers/', '', split(iff(ResourceId startswith '/subscriptions/', strcat('/providers/microsoft.resources/', ResourceId), ResourceId), '/providers/')[-1]);\n let x_ResourceProvider = iff(isempty(providerPath), '', split(providerPath, '/')[0]);\n let tmp_ResourceProviderPath = iff(isempty(providerPath), '', substring(providerPath, strlen(x_ResourceProvider) + 1));\n let segments = split(tmp_ResourceProviderPath, '/');\n let ResourceName = trim(@'/+', replace_string(strcat_array(array_iff(\n dynamic([false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true]),\n segments, dynamic([])), '/'), '//', '/'));\n let x_ResourceTypePath = trim(@'/+', replace_string(strcat_array(array_iff(\n dynamic([true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]),\n segments, dynamic([])), '/'), '//', '/'));\n let xRT = iff(isempty(x_ResourceProvider) or isempty(x_ResourceTypePath), '', strcat(x_ResourceProvider, '/', x_ResourceTypePath));\n // TODO: Remove ResourceType in 0.9\n bag_pack('ResourceId', ResourceId, 'ResourceName', ResourceName, 'ResourceType', xRT, 'SubAccountId', SubAccountId, 'x_ResourceGroupName', x_ResourceGroupName, 'x_ResourceProvider', x_ResourceProvider, 'x_ResourceType', xRT)\n}\n", - "$fxv#8": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| ActualCosts |====================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_raw table -- Create the table if it doesn't exist\n.create-merge table ActualCosts_raw ( ignore: string )\n\n// ActualCosts_raw table -- Remove all columns to allow changing column types\n.alter table ActualCosts_raw ( ignore: string )\n\n// ActualCosts_raw table -- Redefine all columns\n.alter table ActualCosts_raw (\n AccountName: string,\n AccountOwnerId: string,\n AdditionalInfo: string,\n AvailabilityZone: string,\n BillingAccountId: string, \n BillingAccountName: string,\n BillingCurrency: string,\n BillingPeriodEndDate: datetime,\n BillingPeriodStartDate: datetime,\n BillingProfileId: string,\n BillingProfileName: string,\n ChargeType: string,\n ConsumedService: string,\n CostCenter: string,\n Cost: real,\n Date: datetime,\n EffectivePrice: real,\n Frequency: string,\n InvoiceSection: string,\n InvoiceSectionId: string,\n IsAzureCreditEligible: bool,\n MeterCategory: string,\n MeterId: string,\n MeterName: string,\n MeterRegion: string,\n MeterSubCategory: string,\n OfferId: string,\n PartNumber: string,\n PlanName: string,\n Product: string,\n ProductOrderId: string,\n ProductOrderName: string,\n PublisherName: string,\n PublisherType: string,\n Quantity: real,\n ReservationId: string,\n ReservationName: string,\n ResourceGroup: string,\n ResourceId: string,\n ResourceLocation: string,\n ResourceName: string,\n ServiceFamily: string,\n ServiceInfo1: string,\n ServiceInfo2: string,\n SubscriptionId: string,\n SubscriptionName: string,\n Tags: string,\n Term: string,\n UnitOfMeasure: string,\n UnitPrice: real\n)\n\n// ActualCosts_raw ingestion mapping\n.create-or-alter table ActualCosts_raw ingestion parquet mapping \"ActualCosts_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\n]\n```\n\n// ActualCosts_raw retention policy (clear historical data)\n.alter-merge table ActualCosts_raw policy retention softdelete = 0d recoverability = disabled\n\n// ActualCosts_raw retention policy (set the user-defined retention period)\n.alter-merge table ActualCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable ActualCosts_raw streaming ingestion (required for Fabric)\n.alter table ActualCosts_raw policy streamingingestion disable\n\n\n//===| AmortizedCosts |=================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_raw table -- Create the table if it doesn't exist\n.create-merge table AmortizedCosts_raw ( ignore: string )\n\n// AmortizedCosts_raw table -- Remove all columns to allow changing column types\n.alter table AmortizedCosts_raw ( ignore: string )\n\n// AmortizedCosts_raw table -- Redefine all columns\n.alter table AmortizedCosts_raw (\n AccountName: string,\n AccountOwnerId: string,\n AdditionalInfo: string,\n AvailabilityZone: string,\n BillingAccountId: string, \n BillingAccountName: string,\n BillingCurrency: string,\n BillingPeriodEndDate: datetime,\n BillingPeriodStartDate: datetime,\n BillingProfileId: string,\n BillingProfileName: string,\n ChargeType: string,\n ConsumedService: string,\n CostCenter: string,\n Cost: real,\n Date: datetime,\n EffectivePrice: real,\n Frequency: string,\n InvoiceSection: string,\n InvoiceSectionId: string,\n IsAzureCreditEligible: bool,\n MeterCategory: string,\n MeterId: string,\n MeterName: string,\n MeterRegion: string,\n MeterSubCategory: string,\n OfferId: string,\n PartNumber: string,\n PlanName: string,\n Product: string,\n ProductOrderId: string,\n ProductOrderName: string,\n PublisherName: string,\n PublisherType: string,\n Quantity: real,\n ReservationId: string,\n ReservationName: string,\n ResourceGroup: string,\n ResourceId: string,\n ResourceLocation: string,\n ResourceName: string,\n ServiceFamily: string,\n ServiceInfo1: string,\n ServiceInfo2: string,\n SubscriptionId: string,\n SubscriptionName: string,\n Tags: string,\n Term: string,\n UnitOfMeasure: string,\n UnitPrice: real\n)\n\n// AmortizedCosts_raw ingestion mapping\n.create-or-alter table AmortizedCosts_raw ingestion parquet mapping \"AmortizedCosts_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\n]\n```\n\n// AmortizedCosts_raw retention policy (clear historical data)\n.alter-merge table AmortizedCosts_raw policy retention softdelete = 0d recoverability = disabled\n\n// AmortizedCosts_raw retention policy (set the user-defined retention period)\n.alter-merge table AmortizedCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable AmortizedCosts_raw streaming ingestion (required for Fabric)\n.alter table AmortizedCosts_raw policy streamingingestion disable\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_raw table -- Create the table if it doesn't exist\n.create-merge table CommitmentDiscountUsage_raw ( ignore: string )\n\n// CommitmentDiscountUsage_raw table -- Remove all columns to allow changing column types\n.alter table CommitmentDiscountUsage_raw ( ignore: string )\n\n// CommitmentDiscountUsage_raw table -- Redefine all columns\n.alter table CommitmentDiscountUsage_raw (\n InstanceFlexibilityGroup: string,\n InstanceFlexibilityRatio: real,\n InstanceId: string,\n Kind: string,\n ReservationId: string,\n ReservationOrderId: string,\n ReservedHours: real,\n SkuName: string,\n TotalReservedQuantity: real,\n UsageDate: datetime,\n UsedHours: real,\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// CommitmentDiscountUsage_raw ingestion mapping\n.create-or-alter table CommitmentDiscountUsage_raw ingestion parquet mapping \"CommitmentDiscountUsage_raw_mapping\"\n```\n[\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\n { \"Column\": \"InstanceId\", \"Properties\": { \"Field\": \"InstanceId\" } },\n { \"Column\": \"Kind\", \"Properties\": { \"Field\": \"Kind\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\n { \"Column\": \"ReservedHours\", \"Properties\": { \"Field\": \"ReservedHours\" } },\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\n { \"Column\": \"TotalReservedQuantity\", \"Properties\": { \"Field\": \"TotalReservedQuantity\" } },\n { \"Column\": \"UsageDate\", \"Properties\": { \"Field\": \"UsageDate\" } },\n { \"Column\": \"UsedHours\", \"Properties\": { \"Field\": \"UsedHours\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// CommitmentDiscountUsage_raw retention policy (clear historical data)\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = 0d recoverability = disabled\n\n// CommitmentDiscountUsage_raw retention policy (set the user-defined retention period)\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable CommitmentDiscountUsage_raw streaming ingestion (required for Fabric)\n.alter table CommitmentDiscountUsage_raw policy streamingingestion disable\n\n\n//===| Costs |==========================================================================================================\n// Supported versions:\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n// - Tencent: 1.0 -- See https://www.tencentcloud.com/document/product/555/67495 / https://www.tencentcloud.com/document/product/555/67496\n// - Alibaba: 1.0 -- See https://www.alibabacloud.com/help/en/user-center/user-guide/export-alibaba-cloud-standard-billing-focus\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_raw table -- Create the table if it doesn't exist\n.create-merge table Costs_raw ( ignore: string )\n\n// Costs_raw table -- Remove all columns to allow changing column types\n.alter table Costs_raw ( ignore: string )\n\n// Costs_raw table -- Redefine all columns\n.alter table Costs_raw (\n AvailabilityZone: string, // FOCUS 0.5+\n BilledCost: real, // FOCUS 0.5+\n BillingAccountId: string, // FOCUS 0.5+\n BillingAccountName: string, // FOCUS 0.5+\n BillingAccountType: string, // Azure 1.0-preview(v1)+\n BillingCurrency: string, // FOCUS 0.5+\n BillingPeriodEnd: datetime, // FOCUS 0.5+\n BillingPeriodStart: datetime, // FOCUS 0.5+\n CapacityReservationId: string, // FOCUS 1.1+\n CapacityReservationStatus: string, // FOCUS 1.1+\n ChargeCategory: string, // FOCUS 1.0-preview+\n ChargeClass: string, // FOCUS 1.0+\n ChargeDescription: string, // FOCUS 1.0+\n ChargeFrequency: string, // FOCUS 1.0+\n ChargePeriodEnd: datetime, // FOCUS 0.5+\n ChargePeriodStart: datetime, // FOCUS 0.5+\n ChargeSubcategory: string, // FOCUS 1.0-preview only\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview+\n CommitmentDiscountId: string, // FOCUS 1.0-preview+\n CommitmentDiscountName: string, // FOCUS 1.0-preview+\n CommitmentDiscountQuantity: real, // FOCUS 1.1+\n CommitmentDiscountStatus: string, // FOCUS 1.0+\n CommitmentDiscountType: string, // FOCUS 1.0-preview+\n CommitmentDiscountUnit: string, // FOCUS 1.1+\n ConsumedQuantity: real, // FOCUS 1.0+\n ConsumedUnit: string, // FOCUS 1.0+\n ContractedCost: real, // FOCUS 1.0+\n ContractedUnitPrice: real, // FOCUS 1.0+\n EffectiveCost: real, // FOCUS 1.0-preview+\n InvoiceId: string, // FOCUS 1.2+\n InvoiceIssuerName: string, // FOCUS 0.5+\n ListCost: real, // FOCUS 1.0-preview+\n ListUnitPrice: real, // FOCUS 1.0-preview+\n PricingCategory: string, // FOCUS 1.0-preview+\n PricingCurrency: string, // FOCUS 1.2+\n PricingQuantity: real, // FOCUS 1.0-preview+\n PricingUnit: string, // FOCUS 1.0-preview+\n ProviderName: string, // FOCUS 0.5+\n PublisherName: string, // FOCUS 0.5+\n Region: string, // FOCUS 0.5-1.0-preview (deprecated)\n RegionId: string, // FOCUS 1.0+\n RegionName: string, // FOCUS 1.0+\n ResourceId: string, // FOCUS 0.5+\n ResourceName: string, // FOCUS 0.5+\n ResourceType: string, // FOCUS 1.0-preview+\n ServiceCategory: string, // FOCUS 0.5+\n ServiceName: string, // FOCUS 0.5+\n ServiceSubcategory: string, // FOCUS 1.1+\n SkuId: string, // FOCUS 1.0-preview+\n SkuMeter: string, // FOCUS 1.1+\n SkuPriceDetails: string, // FOCUS 1.1+\n SkuPriceId: string, // FOCUS 1.0-preview+\n SubAccountId: string, // FOCUS 0.5+\n SubAccountName: string, // FOCUS 0.5+\n SubAccountType: string, // Azure 1.0-preview(v1)+\n Tags: string, // FOCUS 1.0-preview+\n UsageAmount: real, // GCP Jan 2024 -- Removed Mar 2024 (UsageQuantity)\n UsageQuantity: real, // FOCUS 1.0-preview only\n UsageUnit: string, // FOCUS 1.0-preview only\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_AmortizationClass: string, // Azure 1.2-preview+\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingItemCode: string, // Alibaba 1.0+\n x_BillingItemName: string, // Alibaba 1.0+\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_CommodityCode: string, // Alibaba 1.0+\n x_CommodityName: string, // Alibaba 1.0+\n x_ComponentName: string, // Tencent 1.0+\n x_ComponentType: string, // Tencent 1.0+\n x_ContractedCostInUsd: real, // Azure 1.0+\n x_Cost: real, // GCP Jan 2024 -- Removed Jun 2024 (ContractedCost)\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: string, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_CostType: string, // GCP Jan 2024\n x_Credits: string, // GCP Jan 2024\n x_CurrencyConversionRate: real, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: string, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0+\n x_InstanceID: string, // Alibaba 1.0+\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_OnDemandCost: real, // Azure 1.0-preview(v1) only\n x_OnDemandCostInUsd: real, // Azure 1.0-preview(v1) only\n x_OnDemandUnitPrice: real, // Azure 1.0-preview(v1) only\n x_Operation: string, // AWS 1.0\n x_OwnerAccountID: string, // Tencent 1.0+\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\n x_PricingCurrency: string, // Azure 1.0-preview(v1)-1.0r2\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServiceModel: string, // Azure 1.2-preview+\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: string, // Azure 1.0-preview(v1)-1.2-preview\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterName: string, // Azure 1.0-preview(v1)-1.0r2\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuPlanName: string, // Azure 1.2-preview+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string, // Hubs v1_0+\n x_SubproductName: string, // Tencent 1.0+ // cSpell:ignore Subproduct\n x_UsageType: string // AWS 1.0\n)\n\n// Costs_raw ingestion mapping\n.create-or-alter table Costs_raw ingestion parquet mapping \"Costs_raw_mapping\"\n```\n[\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BilledCost\", \"Properties\": { \"Field\": \"BilledCost\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingAccountType\", \"Properties\": { \"Field\": \"BillingAccountType\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEnd\", \"Properties\": { \"Field\": \"BillingPeriodEnd\" } },\n { \"Column\": \"BillingPeriodStart\", \"Properties\": { \"Field\": \"BillingPeriodStart\" } },\n { \"Column\": \"CapacityReservationId\", \"Properties\": { \"Field\": \"CapacityReservationId\" } },\n { \"Column\": \"CapacityReservationStatus\", \"Properties\": { \"Field\": \"CapacityReservationStatus\" } },\n { \"Column\": \"ChargeCategory\", \"Properties\": { \"Field\": \"ChargeCategory\" } },\n { \"Column\": \"ChargeClass\", \"Properties\": { \"Field\": \"ChargeClass\" } },\n { \"Column\": \"ChargeDescription\", \"Properties\": { \"Field\": \"ChargeDescription\" } },\n { \"Column\": \"ChargeFrequency\", \"Properties\": { \"Field\": \"ChargeFrequency\" } },\n { \"Column\": \"ChargePeriodEnd\", \"Properties\": { \"Field\": \"ChargePeriodEnd\" } },\n { \"Column\": \"ChargePeriodStart\", \"Properties\": { \"Field\": \"ChargePeriodStart\" } },\n { \"Column\": \"ChargeSubcategory\", \"Properties\": { \"Field\": \"ChargeSubcategory\" } },\n { \"Column\": \"CommitmentDiscountCategory\", \"Properties\": { \"Field\": \"CommitmentDiscountCategory\" } },\n { \"Column\": \"CommitmentDiscountId\", \"Properties\": { \"Field\": \"CommitmentDiscountId\" } },\n { \"Column\": \"CommitmentDiscountName\", \"Properties\": { \"Field\": \"CommitmentDiscountName\" } },\n { \"Column\": \"CommitmentDiscountQuantity\", \"Properties\": { \"Field\": \"CommitmentDiscountQuantity\" } },\n { \"Column\": \"CommitmentDiscountStatus\", \"Properties\": { \"Field\": \"CommitmentDiscountStatus\" } },\n { \"Column\": \"CommitmentDiscountType\", \"Properties\": { \"Field\": \"CommitmentDiscountType\" } },\n { \"Column\": \"CommitmentDiscountUnit\", \"Properties\": { \"Field\": \"CommitmentDiscountUnit\" } },\n { \"Column\": \"ConsumedQuantity\", \"Properties\": { \"Field\": \"ConsumedQuantity\" } },\n { \"Column\": \"ConsumedUnit\", \"Properties\": { \"Field\": \"ConsumedUnit\" } },\n { \"Column\": \"ContractedCost\", \"Properties\": { \"Field\": \"ContractedCost\" } },\n { \"Column\": \"ContractedUnitPrice\", \"Properties\": { \"Field\": \"ContractedUnitPrice\" } },\n { \"Column\": \"EffectiveCost\", \"Properties\": { \"Field\": \"EffectiveCost\" } },\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\n { \"Column\": \"InvoiceIssuerName\", \"Properties\": { \"Field\": \"InvoiceIssuerName\" } },\n { \"Column\": \"ListCost\", \"Properties\": { \"Field\": \"ListCost\" } },\n { \"Column\": \"ListUnitPrice\", \"Properties\": { \"Field\": \"ListUnitPrice\" } },\n { \"Column\": \"PricingCategory\", \"Properties\": { \"Field\": \"PricingCategory\" } },\n { \"Column\": \"PricingCurrency\", \"Properties\": { \"Field\": \"PricingCurrency\" } },\n { \"Column\": \"PricingQuantity\", \"Properties\": { \"Field\": \"PricingQuantity\" } },\n { \"Column\": \"PricingUnit\", \"Properties\": { \"Field\": \"PricingUnit\" } },\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\n { \"Column\": \"RegionId\", \"Properties\": { \"Field\": \"RegionId\" } },\n { \"Column\": \"RegionName\", \"Properties\": { \"Field\": \"RegionName\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\n { \"Column\": \"ServiceCategory\", \"Properties\": { \"Field\": \"ServiceCategory\" } },\n { \"Column\": \"ServiceName\", \"Properties\": { \"Field\": \"ServiceName\" } },\n { \"Column\": \"ServiceSubcategory\", \"Properties\": { \"Field\": \"ServiceSubcategory\" } },\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\n { \"Column\": \"SkuMeter\", \"Properties\": { \"Field\": \"SkuMeter\" } },\n { \"Column\": \"SkuPriceDetails\", \"Properties\": { \"Field\": \"SkuPriceDetails\" } },\n { \"Column\": \"SkuPriceId\", \"Properties\": { \"Field\": \"SkuPriceId\" } },\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\n { \"Column\": \"SubAccountType\", \"Properties\": { \"Field\": \"SubAccountType\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"UsageAmount\", \"Properties\": { \"Field\": \"UsageAmount\" } },\n { \"Column\": \"UsageQuantity\", \"Properties\": { \"Field\": \"UsageQuantity\" } },\n { \"Column\": \"UsageUnit\", \"Properties\": { \"Field\": \"UsageUnit\" } },\n { \"Column\": \"x_AccountId\", \"Properties\": { \"Field\": \"x_AccountId\" } },\n { \"Column\": \"x_AccountName\", \"Properties\": { \"Field\": \"x_AccountName\" } },\n { \"Column\": \"x_AccountOwnerId\", \"Properties\": { \"Field\": \"x_AccountOwnerId\" } },\n { \"Column\": \"x_AmortizationClass\", \"Properties\": { \"Field\": \"x_AmortizationClass\" } },\n { \"Column\": \"x_BilledCostInUsd\", \"Properties\": { \"Field\": \"x_BilledCostInUsd\" } },\n { \"Column\": \"x_BilledUnitPrice\", \"Properties\": { \"Field\": \"x_BilledUnitPrice\" } },\n { \"Column\": \"x_BillingAccountId\", \"Properties\": { \"Field\": \"x_BillingAccountId\" } },\n { \"Column\": \"x_BillingAccountName\", \"Properties\": { \"Field\": \"x_BillingAccountName\" } },\n { \"Column\": \"x_BillingExchangeRate\", \"Properties\": { \"Field\": \"x_BillingExchangeRate\" } },\n { \"Column\": \"x_BillingExchangeRateDate\", \"Properties\": { \"Field\": \"x_BillingExchangeRateDate\" } },\n { \"Column\": \"x_BillingItemCode\", \"Properties\": { \"Field\": \"x_BillingItemCode\" } },\n { \"Column\": \"x_BillingItemName\", \"Properties\": { \"Field\": \"x_BillingItemName\" } },\n { \"Column\": \"x_BillingProfileId\", \"Properties\": { \"Field\": \"x_BillingProfileId\" } },\n { \"Column\": \"x_BillingProfileName\", \"Properties\": { \"Field\": \"x_BillingProfileName\" } },\n { \"Column\": \"x_ChargeId\", \"Properties\": { \"Field\": \"x_ChargeId\" } },\n { \"Column\": \"x_ContractedCostInUsd\", \"Properties\": { \"Field\": \"x_ContractedCostInUsd\" } },\n { \"Column\": \"x_CommodityCode\", \"Properties\": { \"Field\": \"x_CommodityCode\" } },\n { \"Column\": \"x_CommodityName\", \"Properties\": { \"Field\": \"x_CommodityName\" } },\n { \"Column\": \"x_ComponentName\", \"Properties\": { \"Field\": \"x_ComponentName\" } },\n { \"Column\": \"x_ComponentType\", \"Properties\": { \"Field\": \"x_ComponentType\" } },\n { \"Column\": \"x_Cost\", \"Properties\": { \"Field\": \"x_Cost\" } },\n { \"Column\": \"x_CostAllocationRuleName\", \"Properties\": { \"Field\": \"x_CostAllocationRuleName\" } },\n { \"Column\": \"x_CostCategories\", \"Properties\": { \"Field\": \"x_CostCategories\" } },\n { \"Column\": \"x_CostCenter\", \"Properties\": { \"Field\": \"x_CostCenter\" } },\n { \"Column\": \"x_Credits\", \"Properties\": { \"Field\": \"x_Credits\" } },\n { \"Column\": \"x_CostType\", \"Properties\": { \"Field\": \"x_CostType\" } },\n { \"Column\": \"x_CurrencyConversionRate\", \"Properties\": { \"Field\": \"x_CurrencyConversionRate\" } },\n { \"Column\": \"x_CustomerId\", \"Properties\": { \"Field\": \"x_CustomerId\" } },\n { \"Column\": \"x_CustomerName\", \"Properties\": { \"Field\": \"x_CustomerName\" } },\n { \"Column\": \"x_Discount\", \"Properties\": { \"Field\": \"x_Discount\" } },\n { \"Column\": \"x_EffectiveCostInUsd\", \"Properties\": { \"Field\": \"x_EffectiveCostInUsd\" } },\n { \"Column\": \"x_EffectiveUnitPrice\", \"Properties\": { \"Field\": \"x_EffectiveUnitPrice\" } },\n { \"Column\": \"x_ExportTime\", \"Properties\": { \"Field\": \"x_ExportTime\" } },\n { \"Column\": \"x_InstanceID\", \"Properties\": { \"Field\": \"x_InstanceID\" } },\n { \"Column\": \"x_InvoiceId\", \"Properties\": { \"Field\": \"x_InvoiceId\" } },\n { \"Column\": \"x_InvoiceIssuerId\", \"Properties\": { \"Field\": \"x_InvoiceIssuerId\" } },\n { \"Column\": \"x_InvoiceSectionId\", \"Properties\": { \"Field\": \"x_InvoiceSectionId\" } },\n { \"Column\": \"x_InvoiceSectionName\", \"Properties\": { \"Field\": \"x_InvoiceSectionName\" } },\n { \"Column\": \"x_ListCostInUsd\", \"Properties\": { \"Field\": \"x_ListCostInUsd\" } },\n { \"Column\": \"x_Location\", \"Properties\": { \"Field\": \"x_Location\" } },\n { \"Column\": \"x_OnDemandCost\", \"Properties\": { \"Field\": \"x_OnDemandCost\" } },\n { \"Column\": \"x_OnDemandCostInUsd\", \"Properties\": { \"Field\": \"x_OnDemandCostInUsd\" } },\n { \"Column\": \"x_OnDemandUnitPrice\", \"Properties\": { \"Field\": \"x_OnDemandUnitPrice\" } },\n { \"Column\": \"x_Operation\", \"Properties\": { \"Field\": \"x_Operation\" } },\n { \"Column\": \"x_OwnerAccountID\", \"Properties\": { \"Field\": \"x_OwnerAccountID\" } },\n { \"Column\": \"x_PartnerCreditApplied\", \"Properties\": { \"Field\": \"x_PartnerCreditApplied\" } },\n { \"Column\": \"x_PartnerCreditRate\", \"Properties\": { \"Field\": \"x_PartnerCreditRate\" } },\n { \"Column\": \"x_PricingBlockSize\", \"Properties\": { \"Field\": \"x_PricingBlockSize\" } },\n { \"Column\": \"x_PricingCurrency\", \"Properties\": { \"Field\": \"x_PricingCurrency\" } },\n { \"Column\": \"x_PricingSubcategory\", \"Properties\": { \"Field\": \"x_PricingSubcategory\" } },\n { \"Column\": \"x_PricingUnitDescription\", \"Properties\": { \"Field\": \"x_PricingUnitDescription\" } },\n { \"Column\": \"x_Project\", \"Properties\": { \"Field\": \"x_Project\" } },\n { \"Column\": \"x_PublisherCategory\", \"Properties\": { \"Field\": \"x_PublisherCategory\" } },\n { \"Column\": \"x_PublisherId\", \"Properties\": { \"Field\": \"x_PublisherId\" } },\n { \"Column\": \"x_ResellerId\", \"Properties\": { \"Field\": \"x_ResellerId\" } },\n { \"Column\": \"x_ResellerName\", \"Properties\": { \"Field\": \"x_ResellerName\" } },\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\n { \"Column\": \"x_ResourceType\", \"Properties\": { \"Field\": \"x_ResourceType\" } },\n { \"Column\": \"x_ServiceCode\", \"Properties\": { \"Field\": \"x_ServiceCode\" } },\n { \"Column\": \"x_ServiceId\", \"Properties\": { \"Field\": \"x_ServiceId\" } },\n { \"Column\": \"x_ServiceModel\", \"Properties\": { \"Field\": \"x_ServiceModel\" } },\n { \"Column\": \"x_ServicePeriodEnd\", \"Properties\": { \"Field\": \"x_ServicePeriodEnd\" } },\n { \"Column\": \"x_ServicePeriodStart\", \"Properties\": { \"Field\": \"x_ServicePeriodStart\" } },\n { \"Column\": \"x_SkuDescription\", \"Properties\": { \"Field\": \"x_SkuDescription\" } },\n { \"Column\": \"x_SkuDetails\", \"Properties\": { \"Field\": \"x_SkuDetails\" } },\n { \"Column\": \"x_SkuIsCreditEligible\", \"Properties\": { \"Field\": \"x_SkuIsCreditEligible\" } },\n { \"Column\": \"x_SkuMeterCategory\", \"Properties\": { \"Field\": \"x_SkuMeterCategory\" } },\n { \"Column\": \"x_SkuMeterId\", \"Properties\": { \"Field\": \"x_SkuMeterId\" } },\n { \"Column\": \"x_SkuMeterName\", \"Properties\": { \"Field\": \"x_SkuMeterName\" } },\n { \"Column\": \"x_SkuMeterSubcategory\", \"Properties\": { \"Field\": \"x_SkuMeterSubcategory\" } },\n { \"Column\": \"x_SkuOfferId\", \"Properties\": { \"Field\": \"x_SkuOfferId\" } },\n { \"Column\": \"x_SkuOrderId\", \"Properties\": { \"Field\": \"x_SkuOrderId\" } },\n { \"Column\": \"x_SkuOrderName\", \"Properties\": { \"Field\": \"x_SkuOrderName\" } },\n { \"Column\": \"x_SkuPartNumber\", \"Properties\": { \"Field\": \"x_SkuPartNumber\" } },\n { \"Column\": \"x_SkuPlanName\", \"Properties\": { \"Field\": \"x_SkuPlanName\" } },\n { \"Column\": \"x_SkuRegion\", \"Properties\": { \"Field\": \"x_SkuRegion\" } },\n { \"Column\": \"x_SkuServiceFamily\", \"Properties\": { \"Field\": \"x_SkuServiceFamily\" } },\n { \"Column\": \"x_SkuTerm\", \"Properties\": { \"Field\": \"x_SkuTerm\" } },\n { \"Column\": \"x_SkuTier\", \"Properties\": { \"Field\": \"x_SkuTier\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } },\n { \"Column\": \"x_SubproductName\", \"Properties\": { \"Field\": \"x_SubproductName\" } },\n { \"Column\": \"x_UsageType\", \"Properties\": { \"Field\": \"x_UsageType\" } }\n]\n```\n\n// Costs_raw retention policy (clear historical data)\n.alter-merge table Costs_raw policy retention softdelete = 0d recoverability = disabled\n\n// Costs_raw retention policy (set the user-defined retention period)\n.alter-merge table Costs_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Costs_raw streaming ingestion (required for Fabric)\n.alter table Costs_raw policy streamingingestion disable\n\n\n//===| Prices |=========================================================================================================\n// NOTE: Must be before cost details.\n//\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_raw table -- Create the table if it doesn't exist\n.create-merge table Prices_raw ( ignore: string )\n\n// Prices_raw table -- Remove all columns to allow changing column types\n.alter table Prices_raw ( ignore: string )\n\n// Prices_raw table -- Redefine all columns\n.alter table Prices_raw (\n BasePrice: real, // Azure EA + MCA\n BillingAccountId: string, // Azure MCA\n BillingAccountName: string, // Azure MCA\n BillingCurrency: string, // Azure MCA\n BillingProfileId: string, // Azure MCA\n BillingProfileName: string, // Azure MCA\n Currency: string, // Azure MCA\n CurrencyCode: string, // Azure EA\n EffectiveEndDate: datetime, // Azure MCA\n EffectiveStartDate: datetime, // Azure EA + MCA\n EnrollmentNumber: string, // Azure EA\n IncludedQuantity: real, // Azure EA\n MarketPrice: real, // Azure EA + MCA\n MeterCategory: string, // Azure EA + MCA\n MeterId: string, // Azure MCA\n MeterID: string, // Azure EA\n MeterName: string, // Azure EA + MCA\n MeterRegion: string, // Azure EA + MCA\n MeterSubCategory: string, // Azure EA + MCA\n MeterType: string, // Azure EA + MCA\n OfferID: string, // Azure EA\n PartNumber: string, // Azure EA\n PriceType: string, // Azure EA + MCA\n Product: string, // Azure EA + MCA\n ProductId: string, // Azure MCA\n ProductID: string, // Azure EA\n ServiceFamily: string, // Azure EA + MCA\n SkuId: string, // Azure MCA\n SkuID: string, // Azure EA\n Term: string, // Azure EA + MCA\n TierMinimumUnits: real, // Azure MCA\n UnitOfMeasure: string, // Azure EA + MCA\n UnitPrice: real, // Azure EA + MCA\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Prices_raw ingestion mapping\n.create-or-alter table Prices_raw ingestion parquet mapping \"Prices_raw_mapping\"\n```\n[\n { \"Column\": \"BasePrice\", \"Properties\": { \"Field\": \"BasePrice\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\n { \"Column\": \"CurrencyCode\", \"Properties\": { \"Field\": \"CurrencyCode\" } },\n { \"Column\": \"EffectiveEndDate\", \"Properties\": { \"Field\": \"EffectiveEndDate\" } },\n { \"Column\": \"EffectiveStartDate\", \"Properties\": { \"Field\": \"EffectiveStartDate\" } },\n { \"Column\": \"EnrollmentNumber\", \"Properties\": { \"Field\": \"EnrollmentNumber\" } },\n { \"Column\": \"IncludedQuantity\", \"Properties\": { \"Field\": \"IncludedQuantity\" } },\n { \"Column\": \"MarketPrice\", \"Properties\": { \"Field\": \"MarketPrice\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterID\", \"Properties\": { \"Field\": \"MeterID\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"MeterType\", \"Properties\": { \"Field\": \"MeterType\" } },\n { \"Column\": \"OfferID\", \"Properties\": { \"Field\": \"OfferID\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PriceType\", \"Properties\": { \"Field\": \"PriceType\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductId\", \"Properties\": { \"Field\": \"ProductId\" } },\n { \"Column\": \"ProductID\", \"Properties\": { \"Field\": \"ProductID\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\n { \"Column\": \"SkuID\", \"Properties\": { \"Field\": \"SkuID\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"TierMinimumUnits\", \"Properties\": { \"Field\": \"TierMinimumUnits\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Prices_raw retention policy (clear historical data)\n.alter-merge table Prices_raw policy retention softdelete = 0d recoverability = disabled\n\n// Prices_raw retention policy (set the user-defined retention period)\n.alter-merge table Prices_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Prices_raw streaming ingestion (required for Fabric)\n.alter table Prices_raw policy streamingingestion disable\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_raw table -- Create the table if it doesn't exist\n.create-merge table Recommendations_raw ( ignore: string )\n\n// Recommendations_raw table -- Remove all columns to allow changing column types\n.alter table Recommendations_raw ( ignore: string )\n\n// Recommendations_raw table -- Redefine all columns\n.alter table Recommendations_raw (\n CostWithNoReservedInstances: real, // MS CM EA resv reco 2024-05-01\n CostWithNoReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n FirstUsageDate: datetime, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n InstanceFlexibilityGroup: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n InstanceFlexibilityRatio: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n Location: string, // MS CM EA+MCA resv reco 2024-05-01\n LookBackPeriod: string, // MS CM EA+MCA resv reco 2024-05-01\n MeterId: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n NetSavings: real, // MS CM EA resv reco 2024-05-01\n NetSavingsJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n NormalizedSize: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n ProviderName: string, // Hubs v1_2\n RecommendedQuantity: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n RecommendedQuantityNormalized: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n ResourceId: string, // Hubs v1_2\n ResourceName: string, // Hubs v1_2\n ResourceType: string, // Hubs v1_2, MS CM EA+MCA resv reco 2024-05-01\n Scope: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n SKU: string, // MS CM EA resv reco 2024-05-01\n SkuName: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces\n SkuProperties: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n SubAccountId: string, // Hubs v1_2\n SubAccountName: string, // Hubs v1_2\n SubscriptionId: string, // MS CM EA+MCA resv reco 2024-05-01\n Term: string, // MS CM EA+MCA resv reco 2024-05-01\n TotalCostWithReservedInstances: real, // MS CM EA resv reco 2024-05-01\n TotalCostWithReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n x_EffectiveCostAfter: real, // Hubs v1_2\n x_EffectiveCostBefore: real, // Hubs v1_2\n x_EffectiveCostSavings: real, // Hubs v1_2\n x_RecommendationCategory: string, // Hubs v1_2\n x_RecommendationDate: datetime, // Hubs v1_2\n x_RecommendationDescription: string, // Hubs v1_2\n x_RecommendationDetails: dynamic, // Hubs v1_2\n x_RecommendationId: string, // Hubs v1_2\n x_ResourceGroupName: string, // Hubs v1_2\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Recommendations_raw ingestion mapping\n.create-or-alter table Recommendations_raw ingestion parquet mapping \"Recommendations_raw_mapping\"\n```\n[\n { \"Column\": \"CostWithNoReservedInstances\", \"Properties\": { \"Field\": \"CostWithNoReservedInstances\" } },\n { \"Column\": \"CostWithNoReservedInstancesJson\", \"Properties\": { \"Field\": \"CostWithNoReservedInstancesJson\" } },\n { \"Column\": \"FirstUsageDate\", \"Properties\": { \"Field\": \"FirstUsageDate\" } },\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\n { \"Column\": \"Location\", \"Properties\": { \"Field\": \"Location\" } },\n { \"Column\": \"LookBackPeriod\", \"Properties\": { \"Field\": \"LookBackPeriod\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"NetSavings\", \"Properties\": { \"Field\": \"NetSavings\" } },\n { \"Column\": \"NetSavingsJson\", \"Properties\": { \"Field\": \"NetSavingsJson\" } },\n { \"Column\": \"NormalizedSize\", \"Properties\": { \"Field\": \"NormalizedSize\" } },\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\n { \"Column\": \"RecommendedQuantity\", \"Properties\": { \"Field\": \"RecommendedQuantity\" } },\n { \"Column\": \"RecommendedQuantityNormalized\", \"Properties\": { \"Field\": \"RecommendedQuantityNormalized\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\n { \"Column\": \"Scope\", \"Properties\": { \"Field\": \"Scope\" } },\n { \"Column\": \"SKU\", \"Properties\": { \"Field\": \"SKU\" } },\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\n { \"Column\": \"SkuProperties\", \"Properties\": { \"Field\": \"SkuProperties\" } },\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"TotalCostWithReservedInstances\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstances\" } },\n { \"Column\": \"TotalCostWithReservedInstancesJson\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstancesJson\" } },\n { \"Column\": \"x_EffectiveCostAfter\", \"Properties\": { \"Field\": \"x_EffectiveCostAfter\" } },\n { \"Column\": \"x_EffectiveCostBefore\", \"Properties\": { \"Field\": \"x_EffectiveCostBefore\" } },\n { \"Column\": \"x_EffectiveCostSavings\", \"Properties\": { \"Field\": \"x_EffectiveCostSavings\" } },\n { \"Column\": \"x_RecommendationCategory\", \"Properties\": { \"Field\": \"x_RecommendationCategory\" } },\n { \"Column\": \"x_RecommendationDate\", \"Properties\": { \"Field\": \"x_RecommendationDate\" } },\n { \"Column\": \"x_RecommendationDescription\", \"Properties\": { \"Field\": \"x_RecommendationDescription\" } },\n { \"Column\": \"x_RecommendationDetails\", \"Properties\": { \"Field\": \"x_RecommendationDetails\" } },\n { \"Column\": \"x_RecommendationId\", \"Properties\": { \"Field\": \"x_RecommendationId\" } },\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Recommendations_raw retention policy (clear historical data)\n.alter-merge table Recommendations_raw policy retention softdelete = 0d recoverability = disabled\n\n// Recommendations_raw retention policy (set the user-defined retention period)\n.alter-merge table Recommendations_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Recommendations_raw streaming ingestion (required for Fabric)\n.alter table Recommendations_raw policy streamingingestion disable\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_raw table -- Create the table if it doesn't exist\n.create-merge table Transactions_raw ( ignore: string )\n\n// Transactions_raw table -- Remove all columns to allow changing column types\n.alter table Transactions_raw ( ignore: string )\n\n// Transactions_raw table -- Redefine all columns\n.alter table Transactions_raw (\n AccountName: string, // MS CM EA resv trans 2023-05-01\n AccountOwnerEmail: string, // MS CM EA resv trans 2023-05-01\n Amount: real, // MS CM EA+MCA resv trans 2023-05-01\n ArmSkuName: string, // MS CM EA+MCA resv trans 2023-05-01\n BillingFrequency: string, // MS CM EA+MCA resv trans 2023-05-01\n BillingMonth: string, // MS CM EA resv trans 2023-05-01\n BillingProfileId: string, // MS CM MCA resv trans 2023-05-01\n BillingProfileName: string, // MS CM MCA resv trans 2023-05-01\n CostCenter: string, // MS CM EA resv trans 2023-05-01\n Currency: string, // MS CM EA+MCA resv trans 2023-05-01\n CurrentEnrollmentId: string, // MS CM EA resv trans 2023-05-01\n DepartmentName: string, // MS CM EA resv trans 2023-05-01\n Description: string, // MS CM EA+MCA resv trans 2023-05-01\n EventDate: datetime, // MS CM EA+MCA resv trans 2023-05-01\n EventType: string, // MS CM EA+MCA resv trans 2023-05-01\n Invoice: string, // MS CM EA+MCA resv trans 2023-05-01\n InvoiceId: string, // MS CM EA+MCA resv trans 2023-05-01\n InvoiceSectionId: string, // MS CM MCA resv trans 2023-05-01\n InvoiceSectionName: string, // MS CM MCA resv trans 2023-05-01\n MonetaryCommitment: real, // MS CM EA resv trans 2023-05-01\n Overage: real, // MS CM EA resv trans 2023-05-01\n PurchasingEnrollment: string, // MS CM EA resv trans 2023-05-01\n PurchasingSubscriptionGuid: string, // MS CM EA+MCA resv trans 2023-05-01\n PurchasingSubscriptionName: string, // MS CM EA+MCA resv trans 2023-05-01\n Quantity: real, // MS CM EA+MCA resv trans 2023-05-01\n Region: string, // MS CM EA+MCA resv trans 2023-05-01\n ReservationOrderId: string, // MS CM EA+MCA resv trans 2023-05-01\n ReservationOrderName: string, // MS CM EA+MCA resv trans 2023-05-01\n Term: string, // MS CM EA+MCA resv trans 2023-05-01\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Transactions_raw ingestion mapping\n.create-or-alter table Transactions_raw ingestion parquet mapping \"Transactions_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerEmail\", \"Properties\": { \"Field\": \"AccountOwnerEmail\" } },\n { \"Column\": \"Amount\", \"Properties\": { \"Field\": \"Amount\" } },\n { \"Column\": \"ArmSkuName\", \"Properties\": { \"Field\": \"ArmSkuName\" } },\n { \"Column\": \"BillingFrequency\", \"Properties\": { \"Field\": \"BillingFrequency\" } },\n { \"Column\": \"BillingMonth\", \"Properties\": { \"Field\": \"BillingMonth\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\n { \"Column\": \"CurrentEnrollmentId\", \"Properties\": { \"Field\": \"CurrentEnrollmentId\" } },\n { \"Column\": \"DepartmentName\", \"Properties\": { \"Field\": \"DepartmentName\" } },\n { \"Column\": \"Description\", \"Properties\": { \"Field\": \"Description\" } },\n { \"Column\": \"EventDate\", \"Properties\": { \"Field\": \"EventDate\" } },\n { \"Column\": \"EventType\", \"Properties\": { \"Field\": \"EventType\" } },\n { \"Column\": \"Invoice\", \"Properties\": { \"Field\": \"Invoice\" } },\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"InvoiceSectionName\", \"Properties\": { \"Field\": \"InvoiceSectionName\" } },\n { \"Column\": \"MonetaryCommitment\", \"Properties\": { \"Field\": \"MonetaryCommitment\" } },\n { \"Column\": \"Overage\", \"Properties\": { \"Field\": \"Overage\" } },\n { \"Column\": \"PurchasingEnrollment\", \"Properties\": { \"Field\": \"PurchasingEnrollment\" } },\n { \"Column\": \"PurchasingSubscriptionGuid\", \"Properties\": { \"Field\": \"PurchasingSubscriptionGuid\" } },\n { \"Column\": \"PurchasingSubscriptionName\", \"Properties\": { \"Field\": \"PurchasingSubscriptionName\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\n { \"Column\": \"ReservationOrderName\", \"Properties\": { \"Field\": \"ReservationOrderName\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Transactions_raw retention policy (clear historical data)\n.alter-merge table Transactions_raw policy retention softdelete = 0d recoverability = disabled\n\n// Transactions_raw retention policy (set the user-defined retention period)\n.alter-merge table Transactions_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Transactions_raw streaming ingestion (required for Fabric)\n.alter table Transactions_raw policy streamingingestion disable\n\n", - "$fxv#9": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Prices |=========================================================================================================\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All prices transformed to FOCUS 1.0. Use Prices_transform_v1_2() instead.', folder='Prices')\nPrices_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n let prices = materialize(\n Prices_raw\n //\n // Change real to decimal\n | extend\n BasePrice = todecimal(BasePrice),\n IncludedQuantity = todecimal(IncludedQuantity),\n MarketPrice = todecimal(MarketPrice),\n TierMinimumUnits = todecimal(TierMinimumUnits),\n UnitPrice = todecimal(UnitPrice)\n //\n | extend x_SkuId = coalesce(SkuId, SkuID)\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\n | extend x_SkuTerm = isoMonths(Term)\n | project-rename\n x_BaseUnitPrice = BasePrice,\n x_EffectivePeriodEnd = EffectiveEndDate,\n x_EffectivePeriodStart = EffectiveStartDate,\n x_PricingUnitDescription = UnitOfMeasure,\n x_SkuIncludedQuantity = IncludedQuantity,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuMeterType = MeterType,\n x_SkuOfferId = OfferID,\n x_SkuPartNumber = PartNumber,\n x_SkuPriceType = PriceType,\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTier = TierMinimumUnits\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, todecimal('')) // UnitPrice for savings plan is not the on-demand unit price\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, todecimal('')) // MarketPrice for savings plan is not the list price\n | extend ChargeCategory = case(\n x_SkuPriceType == 'Consumption', 'Usage',\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\n ''\n )\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\n //\n // Get latest ingested row based on the unique ID\n | extend x_IngestionTime = ingestion_time()\n );\n //\n // Meters for reservations and savings plans to identify commitment eligibility\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\n //\n // Copy list/base/contracted prices from on-demand SKUs\n prices\n | where x_SkuPriceType == 'SavingsPlan'\n // If we use join, specify the shuffle key\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\n //\n // Calculate commitment discount elgibility\n // TODO: Would a join be faster?\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\n //\n // Add PricingUnit and x_PricingBlockSize\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\n | lookup kind=leftouter (PricingUnits | extend x_PricingBlockSize = todecimal(x_PricingBlockSize)) on x_PricingUnitDescription\n //\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, todecimal('')) // Savings plan prices are for the effective price, not the contracted price\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\n | project\n BillingAccountId = tolower(case(\n BillingProfileId startswith '/', BillingProfileId,\n BillingAccountId startswith '/', BillingAccountId,\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\n )),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\n ChargeCategory,\n CommitmentDiscountCategory = case(\n x_SkuPriceType == 'ReservedInstance', 'Usage',\n x_SkuPriceType == 'SavingsPlan', 'Spend',\n ''\n ),\n CommitmentDiscountType = case(\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\n ''\n ),\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory = case(\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed',\n ''\n ),\n PricingUnit,\n SkuId = coalesce(ProductId, ProductID),\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement = case(\n strlen(x_BillingAccountId) > 32, 'MCA',\n strlen(x_BillingAccountId) < 32, 'EA',\n 'Unknown'\n ),\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingCurrency = coalesce(Currency, CurrencyCode), // CurrencyCode last as a fallback only\n x_PricingSubcategory = case(\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\n ''\n ),\n x_PricingUnitDescription,\n x_SkuDescription = Product,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\n}\n\n// Prices_final_v1_0 table\n.create-merge table Prices_final_v1_0 (\n BillingAccountId: string,\n BillingAccountName: string,\n BillingCurrency: string,\n ChargeCategory: string,\n CommitmentDiscountCategory: string,\n CommitmentDiscountType: string,\n ContractedUnitPrice: decimal,\n ListUnitPrice: decimal,\n PricingCategory: string,\n PricingUnit: string,\n SkuId: string,\n SkuPriceId: string,\n SkuPriceIdv2: string, // Hubs add-on\n x_BaseUnitPrice: decimal, // Azure\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure MCA\n x_BillingProfileId: string, // Azure MCA\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_ContractedUnitPriceDiscount: decimal, // Hubs add-on\n x_ContractedUnitPriceDiscountPercent: decimal, // Hubs add-on\n x_EffectivePeriodEnd: datetime, // Azure\n x_EffectivePeriodStart: datetime, // Azure\n x_EffectiveUnitPrice: decimal, // Azure\n x_EffectiveUnitPriceDiscount: decimal, // Hubs add-on\n x_EffectiveUnitPriceDiscountPercent: decimal, // Hubs add-on\n x_IngestionTime: datetime, // Hubs add-on\n x_PricingBlockSize: decimal, // Hubs add-on\n x_PricingCurrency: string, // Azure\n x_PricingSubcategory: string, // Hubs add-on\n x_PricingUnitDescription: string, // Azure\n x_SkuDescription: string, // Azure\n x_SkuId: string, // Azure\n x_SkuIncludedQuantity: decimal, // Azure EA\n x_SkuMeterCategory: string, // Azure\n x_SkuMeterId: string, // Azure\n x_SkuMeterName: string, // Azure\n x_SkuMeterSubcategory: string, // Azure\n x_SkuMeterType: string, // Azure\n x_SkuPriceType: string, // Azure\n x_SkuProductId: string, // Azure\n x_SkuRegion: string, // Azure\n x_SkuServiceFamily: string, // Azure\n x_SkuOfferId: string, // Azure EA\n x_SkuPartNumber: string, // Azure EA\n x_SkuTerm: int, // Azure\n x_SkuTier: decimal, // Azure MCA\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_TotalUnitPriceDiscount: decimal, // Hubs add-on\n x_TotalUnitPriceDiscountPercent: decimal // Hubs add-on\n)\n\n// Update policy for Prices_raw -> Prices_final_v1_0\n.alter table Prices_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Prices_raw\",\n \"Query\": \"Prices_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Cost and usage |=================================================================================================\n// Supported versions:\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All costs transformed to FOCUS 1.0. Use Costs_transform_v1_2() instead.', folder='Costs')\nCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n Costs_raw\n //\n // Change real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n ContractedCost = todecimal(ContractedCost),\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n EffectiveCost = todecimal(EffectiveCost),\n ListCost = todecimal(ListCost),\n ListUnitPrice = todecimal(ListUnitPrice),\n PricingQuantity = todecimal(PricingQuantity),\n UsageAmount = todecimal(UsageAmount),\n UsageQuantity = todecimal(UsageQuantity),\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\n x_Cost = todecimal(x_Cost),\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\n x_OnDemandCost = todecimal(x_OnDemandCost),\n x_OnDemandCostInUsd = todecimal(x_OnDemandCostInUsd),\n x_OnDemandUnitPrice = todecimal(x_OnDemandUnitPrice),\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\n //\n // Dedupe rows\n | extend x_IngestionTime = ingestion_time()\n | extend x_ChargeId = ''\n // TODO: Consider adding a unique charge ID per row\n // hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // // 1. Resource hierarchy (including resource name), highest to lowest\n // BillingAccountId,\n // x_InvoiceSectionId,\n // x_AccountOwnerId,\n // SubAccountId,\n // x_ResourceGroupName,\n // ResourceName,\n // // 2. Resource details\n // ResourceId,\n // RegionId,\n // Tags,\n // CommitmentDiscountId,\n // x_CostCenter,\n // // 4. Meter details\n // SkuPriceId,\n // x_SkuMeterId,\n // x_SkuPartNumber,\n // x_SkuOfferId,\n // x_SkuDetails,\n // // 5. Date\n // ChargePeriodStart\n // ))\n //\n // Identify data quality issues\n | extend x_SourceChanges = trim_end(',', strcat(\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\n 'XEffectiveUnitPriceRoundingError,', ''),\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\n ))\n //\n // Fix columns needed in other changes\n | extend ProviderName = case(\n isnotempty(ProviderName), ProviderName,\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\n ''\n )\n //\n // Identify source\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\n ''\n ))\n // Append version check error code\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\n )\n //\n // Fix quantities\n | extend PricingQuantity = case(\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\n PricingQuantity\n )\n | extend ConsumedQuantity = case(\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, decimal(1)),\n ConsumedQuantity\n )\n //\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\n and (ListUnitPrice == 0 or ContractedUnitPrice == 0)\n and x_EffectiveUnitPrice != 0\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\n | as allCosts\n | where tmp_MissingPrices\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | as costsWithMissingPrices\n | join kind=leftouter (\n Prices_final_v1_0\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\n ) on tmp_ReservationPriceLookupKey\n //\n // Select the best price to use for each row\n // TODO: Save values before changing -- | extend x_old_ContractedUnitPrice = ContractedUnitPrice, x_old_EffectiveUnitPrice = x_EffectiveUnitPrice, x_old_ListUnitPrice = ListUnitPrice, x_old_ListCost = ListCost, x_old_ContractedCost = ContractedCost\n | extend x_EffectiveUnitPrice = case(\n // If price is a rounding error away from the billed price, use the billed price\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\n // If price is a rounding error away from the contracted price, use the contracted price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\n x_EffectiveUnitPrice\n )\n | extend ContractedUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\n x_EffectiveUnitPrice\n )\n | extend ListUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // Otherwise, assume the contracted price is the same as list price to support aggregations\n ContractedUnitPrice\n )\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\n | extend ContractedCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\n // ContractedCost is 0 in all other scenarios...\n // If 0 and there's a billed cost and prices are the same, use BilledCost\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume EffectiveCost\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\n // Fall back to the original value for any unhandled scenarios\n ContractedCost\n )\n | extend ListCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\n // ListCost is 0 in all other scenarios...\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume ContractedCost\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\n // Fall back to the original value for any unhandled scenarios\n ListCost\n )\n // Merge the rest of the unmodified cost records and remove excess columns\n | union (allCosts | where not(tmp_MissingPrices))\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\n //\n // BUG: Fix ContractedCost that has bad values\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\n //\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), todecimal(''))\n | extend ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\n //\n // Convert IDs to lowercase for consistency\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\n //\n // BUG: Remove EffectiveCost for commitment discount purchases\n | extend EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), EffectiveCost)\n | extend x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), x_EffectiveCostInUsd)\n //\n // Clean up resource columns\n | extend ResourceId = case(\n isnotempty(ResourceId), ResourceId,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\n ResourceId)\n | extend ResourceName = tolower(case(\n isnotempty(ResourceName), ResourceName,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\n ResourceName))\n | extend x_ResourceType = case(\n isnotempty(x_ResourceType), x_ResourceType,\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\n x_ResourceType)\n | extend ResourceType = case(\n // Use existing resource type display name unless it's an internal resource type ID\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\n // Use CommitmentDiscountType for commitment discount purchases\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\n // Look up display name from internal type\n isnotempty(x_ResourceType), coalesce(resource_type(x_ResourceType).SingularDisplayName, ResourceType, x_ResourceType),\n ResourceType)\n //\n // Sort columns and apply final transforms\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId = tolower(BillingAccountId),\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEnd),\n BillingPeriodStart = startofmonth(BillingPeriodStart),\n ChargeCategory = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Credit', 'Credit',\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\n ChargeCategory\n ),\n ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass),\n ChargeDescription,\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\n ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency),\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId = tolower(CommitmentDiscountId),\n CommitmentDiscountName,\n CommitmentDiscountStatus = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Used Commitment', 'Used',\n ChargeSubcategory == 'Unused Commitment', 'Unused',\n CommitmentDiscountStatus\n ),\n CommitmentDiscountType,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\n EffectiveCost,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory = case(\n // Handle FOCUS 1.0-preview PricingCategory values\n PricingCategory == 'On-Demand', 'Standard',\n PricingCategory == 'Commitment-Based', 'Committed',\n PricingCategory\n ),\n PricingQuantity,\n PricingUnit,\n ProviderName,\n // Handle missing PublisherName values\n PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, ''),\n // Handle FOCUS 1.0-preview Region column\n RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region)),\n RegionName = coalesce(RegionName, Region),\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SkuId,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType, // Azure 1.0-preview(v1)+\n Tags = parse_json(Tags),\n x_AccountId, // Azure 1.0-preview(v1)+\n x_AccountName, // Azure 1.0-preview(v1)+\n x_AccountOwnerId, // Azure 1.0-preview(v1)+\n x_BilledCostInUsd, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement = case(\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\n ProviderName\n ), // Hubs add-on\n x_BillingAccountId, // Azure 1.0-preview(v1)+\n x_BillingAccountName, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate, // Azure 1.0-preview(v1)+\n x_BillingProfileId, // Azure 1.0-preview(v1)+\n x_BillingProfileName, // Azure 1.0-preview(v1)+\n x_ChargeId, // Azure 1.0-preview(v1) only\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd), // Azure 1.0+\n x_CostAllocationRuleName, // Azure 1.0-preview(v1)+\n x_CostCategories = parse_json(x_CostCategories), // AWS 1.0 (JSON)\n x_CostCenter, // Azure 1.0-preview(v1)+\n x_Credits = parse_json(x_Credits), // GCP Jan 2024\n x_CostType, // GCP Jan 2024\n x_CurrencyConversionRate, // GCP Jun 2024\n x_CustomerId, // Azure 1.0-preview(v1)+\n x_CustomerName, // Azure 1.0-preview(v1)+\n x_Discount = parse_json(x_Discount), // AWS 1.0 (JSON)\n x_EffectiveCostInUsd, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice, // Azure 1.0-preview(v1)+\n x_ExportTime, // GCP Jan 2024\n x_IngestionTime, // Hubs add-on\n x_InvoiceId = coalesce(InvoiceId, x_InvoiceId), // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId = case( // Azure 1.0-preview(v1)+\n x_InvoiceSectionId == '-2', '',\n x_InvoiceSectionId\n ),\n x_InvoiceSectionName = case( // Azure 1.0-preview(v1)+\n x_InvoiceSectionName == 'Unassigned', '',\n x_InvoiceSectionName\n ),\n x_ListCostInUsd, // Azure 1.0-preview(v1)+\n x_Location, // GCP Jan 2024\n x_Operation, // AWS 1.0\n x_PartnerCreditApplied, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate, // Azure 1.0-preview(v1)+\n x_PricingBlockSize, // Azure 1.0-preview(v1)+\n x_PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency), // Azure 1.0-preview(v1)+\n x_PricingSubcategory, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription, // Azure 1.0-preview(v1)+\n x_Project, // GCP Jan 2024\n x_PublisherCategory, // Azure 1.0-preview(v1)+\n x_PublisherId, // Azure 1.0-preview(v1)+\n x_ResellerId, // Azure 1.0-preview(v1)+\n x_ResellerName, // Azure 1.0-preview(v1)+\n x_ResourceGroupName = tolower(x_ResourceGroupName), // Azure 1.0-preview(v1)+\n x_ResourceType, // Azure 1.0-preview(v1)+\n x_ServiceCode, // AWS 1.0\n x_ServiceId, // GCP Jan 2024\n x_ServicePeriodEnd, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart, // Azure 1.0-preview(v1)+\n x_SkuDescription, // Azure 1.0-preview(v1)+\n x_SkuDetails = parse_json(x_SkuDetails), // Azure 1.0-preview(v1)+\n x_SkuIsCreditEligible, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory, // Azure 1.0-preview(v1)+\n x_SkuMeterId, // Azure 1.0-preview(v1)+\n x_SkuMeterName = coalesce(SkuMeter, x_SkuMeterName), // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory, // Azure 1.0-preview(v1)+\n x_SkuOfferId, // Azure 1.0-preview(v1)+\n x_SkuOrderId, // Azure 1.0-preview(v1)+\n x_SkuOrderName, // Azure 1.0-preview(v1)+\n x_SkuPartNumber, // Azure 1.0-preview(v1)+\n x_SkuRegion, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily, // Azure 1.0-preview(v1)+\n x_SkuTerm, // Azure 1.0-preview(v1)+\n x_SkuTier, // Azure 1.0-preview(v1)+\n x_SourceChanges, // Hubs add-on\n x_SourceName, // Hubs add-on\n x_SourceProvider, // Hubs add-on\n x_SourceType, // Hubs add-on\n x_SourceVersion, // Hubs add-on\n x_UsageType // AWS 1.0\n}\n\n// Costs_final_v1_0 table\n.create-merge table Costs_final_v1_0 (\n AvailabilityZone: string,\n BilledCost: decimal,\n BillingAccountId: string,\n BillingAccountName: string,\n BillingAccountType: string, // Azure 1.0-preview(v1)+\n BillingCurrency: string,\n BillingPeriodEnd: datetime,\n BillingPeriodStart: datetime,\n ChargeCategory: string,\n ChargeClass: string,\n ChargeDescription: string,\n ChargeFrequency: string,\n ChargePeriodEnd: datetime,\n ChargePeriodStart: datetime,\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview only\n CommitmentDiscountId: string,\n CommitmentDiscountName: string,\n CommitmentDiscountStatus: string,\n CommitmentDiscountType: string,\n ConsumedQuantity: decimal,\n ConsumedUnit: string,\n ContractedCost: decimal,\n ContractedUnitPrice: decimal,\n EffectiveCost: decimal,\n InvoiceIssuerName: string,\n ListCost: decimal,\n ListUnitPrice: decimal,\n PricingCategory: string,\n PricingQuantity: decimal,\n PricingUnit: string,\n ProviderName: string,\n PublisherName: string,\n RegionId: string,\n RegionName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n ServiceCategory: string,\n ServiceName: string,\n SkuId: string,\n SkuPriceId: string,\n SubAccountId: string,\n SubAccountName: string,\n SubAccountType: string,\n Tags: dynamic,\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_BilledCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: decimal, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: decimal, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_ContractedCostInUsd: decimal, // Azure 1.0+\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_Credits: dynamic, // GCP Jan 2024\n x_CostType: string, // GCP Jan 2024\n x_CurrencyConversionRate: decimal, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: dynamic, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: decimal, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024\n x_IngestionTime: datetime, // Hubs add-on\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_Operation: string, // AWS 1.0\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: decimal, // Azure 1.0-preview(v1)+\n x_PricingCurrency: string, // Azure 1.0-preview(v1)+\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterName: string, // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceChanges: string, // Hubs add-on\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_UsageType: string // AWS 1.0\n)\n\n// Update policy for Costs_raw -> Costs_final_v1_0 table\n.alter table Costs_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Costs_raw\",\n \"Query\": \"Costs_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Actual costs |===================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use ActualCosts_transform_v1_2() instead.', folder='Costs')\nActualCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n // TODO: Transform actual costs to FOCUS 1.0 format\n ActualCosts_raw\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodStart = Date,\n ChargePeriodEnd = Date + 1d,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = '',\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory = '',\n SkuId = '',\n SkuMeter = '',\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = SubscriptionName,\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentName = '',\n x_ComponentType = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel = '',\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for ActualCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": false,\n \"Source\": \"ActualCosts_raw\",\n \"Query\": \"ActualCosts_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Amortized costs |================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use AmortizedCosts_transform_v1_2() instead.', folder='Costs')\nAmortizedCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n // TODO: Transform actual costs to FOCUS 1.0 format\n AmortizedCosts_raw\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodStart = Date,\n ChargePeriodEnd = Date + 1d,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = '',\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory = '',\n SkuId = '',\n SkuMeter = '',\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = SubscriptionName,\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentName = '',\n x_ComponentType = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel = '',\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for AmortizedCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": false,\n \"Source\": \"AmortizedCosts_raw\",\n \"Query\": \"AmortizedCosts_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All commitment discount usage transformed to FOCUS 1.0. This includes reservationdeatils_raw. Use CommitmentDiscountUsage_transform_v1_2() instead.', folder='Commitment discounts')\nCommitmentDiscountUsage_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n CommitmentDiscountUsage_raw\n //\n // Change real to decimal\n | extend\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\n ReservedHours = todecimal(ReservedHours),\n TotalReservedQuantity = todecimal(TotalReservedQuantity),\n UsedHours = todecimal(UsedHours)\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Handle resource columns\n | extend ResourceId = tolower(InstanceId)\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, x_ServiceModel) on x_ResourceType\n //\n // Sort columns and apply final transforms\n | project\n ChargePeriodEnd = UsageDate + 1d,\n ChargePeriodStart = UsageDate,\n CommitmentDiscountCategory = 'Usage',\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\n CommitmentDiscountType = 'Reservation',\n ConsumedQuantity = UsedHours,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SubAccountId,\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\n x_CommitmentDiscountCommittedAmount = ReservedHours,\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\n x_CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\n x_IngestionTime = ingestion_time(),\n x_ResourceGroupName,\n x_ResourceType,\n // x_RowId = hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // CommitmentDiscountId,\n // ResourceId,\n // ChargePeriodStart\n // )),\n x_ServiceModel,\n x_SkuOrderId = ReservationOrderId,\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\n}\n\n// CommitmentDiscountUsage_final_v1_0 table\n.create-merge table CommitmentDiscountUsage_final_v1_0 (\n ChargePeriodEnd: datetime, // Hubs add-on\n ChargePeriodStart: datetime, // MS 2023-03-01\n CommitmentDiscountCategory: string, // Hubs add-on\n CommitmentDiscountId: string, // MS 2023-03-01\n CommitmentDiscountType: string, // Hubs add-on\n ConsumedQuantity: decimal, // MS 2023-03-01\n ProviderName: string, // Hubs add-on\n ResourceId: string, // MS 2023-03-01\n ResourceName: string, // Hubs add-on\n ResourceType: string, // Hubs add-on\n ServiceCategory: string, // Hubs add-on\n ServiceName: string, // Hubs add-on\n SubAccountId: string, // Hubs add-on\n x_CommitmentDiscountCommittedCount: decimal, // MS 2023-03-01\n x_CommitmentDiscountCommittedAmount: decimal, // MS 2023-03-01\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\n x_CommitmentDiscountNormalizedRatio: decimal, // MS 2023-03-01\n x_CommitmentDiscountQuantity: decimal, // MS 2023-03-01\n x_IngestionTime: datetime, // Hubs add-on\n x_ResourceGroupName: string, // Hubs add-on\n x_ResourceType: string, // Hubs add-on\n x_ServiceModel: string, // Hubs add-on\n x_SkuOrderId: string, // MS 2023-03-01\n x_SkuSize: string, // MS 2023-03-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string // Hubs add-on\n)\n\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_0 table\n.alter table CommitmentDiscountUsage_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"CommitmentDiscountUsage_raw\",\n \"Query\": \"CommitmentDiscountUsage_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All recommendations transformed to FOCUS 1.0. Use Recommendations_transform_v1_2() instead.', folder='Recommendations')\nRecommendations_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Recommendations_raw\n //\n // Change real to decimal\n | extend\n CostWithNoReservedInstances = todecimal(CostWithNoReservedInstances),\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\n NetSavings = todecimal(NetSavings),\n RecommendedQuantity = todecimal(RecommendedQuantity),\n RecommendedQuantityNormalized = todecimal(RecommendedQuantityNormalized),\n TotalCostWithReservedInstances = todecimal(TotalCostWithReservedInstances)\n //\n | extend x_IngestionTime = ingestion_time()\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Convert JSON cost columns to decimal\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\n //\n // Build recommendation details\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\n | extend x_RecommendationDetails = case(\n x_SourceType == 'ReservationRecommendations', bag_pack(\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\n 'CommitmentDiscountResourceType', ResourceType,\n 'CommitmentDiscountScope', Scope,\n 'LookbackPeriodDuration', case(\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\n ''\n ),\n 'LookbackPeriodStart', FirstUsageDate,\n 'RecommendedQuantity', RecommendedQuantity,\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\n 'RegionId', Location,\n 'RegionName', RegionName,\n 'SkuMeterId', MeterId,\n 'SkuPriceDetails', SkuProperties,\n 'SkuSize', coalesce(SKU, SkuName),\n 'SkuTerm', isoMonths(Term)\n ),\n dynamic({})\n )\n //\n // Sort columns and apply final transforms\n | extend x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d)\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\n | project\n ProviderName,\n SubAccountId = iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), ''),\n x_IngestionTime,\n x_EffectiveCostAfter = TotalCostWithReservedInstances,\n x_EffectiveCostBefore = CostWithNoReservedInstances,\n x_EffectiveCostSavings = NetSavings,\n x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d),\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n// Recommendations_final_v1_0 table\n.create-merge table Recommendations_final_v1_0 (\n ProviderName: string,\n SubAccountId: string,\n x_IngestionTime: datetime,\n x_EffectiveCostAfter: decimal,\n x_EffectiveCostBefore: decimal,\n x_EffectiveCostSavings: decimal,\n x_RecommendationDate: datetime,\n x_RecommendationDetails: dynamic,\n x_SourceName: string,\n x_SourceProvider: string,\n x_SourceType: string,\n x_SourceVersion: string\n)\n\n// Update policy for Recommendations_raw -> Recommendations_final_v1_0 table\n.alter table Recommendations_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Recommendations_raw\",\n \"Query\": \"Recommendations_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All transactions transformed to FOCUS 1.0. Use Transactions_transform_v1_2() instead.', folder='Transactions')\nTransactions_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Transactions_raw\n //\n // Change real to decimal\n | extend\n Amount = todecimal(Amount),\n MonetaryCommitment = todecimal(MonetaryCommitment),\n Overage = todecimal(Overage),\n Quantity = todecimal(Quantity)\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Handle BillingPeriodStart/End\n | extend BillingMonth = tostring(BillingMonth)\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\n //\n // Sort columns and apply final transforms\n | project\n BilledCost = Amount,\n BillingAccountId = case(\n BillingProfileId startswith '/', BillingProfileId,\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\n ''\n ),\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\n BillingCurrency = Currency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory = case(\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = case(\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\n EventType == 'Refund', 'Correction',\n ''\n ),\n ChargeDescription = Description,\n ChargeFrequency = case(\n BillingFrequency == 'OneTime', 'One-Time',\n BillingFrequency == 'Recurring', 'Recurring',\n BillingFrequency\n ),\n ChargePeriodStart = EventDate,\n PricingQuantity = Quantity,\n PricingUnit = 'Reservations',\n ProviderName,\n RegionId = Region,\n RegionName = Region,\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerEmail,\n x_CostCenter = CostCenter,\n x_InvoiceId = InvoiceId,\n x_InvoiceNumber = Invoice,\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\n x_IngestionTime = ingestion_time(),\n x_MonetaryCommitment = MonetaryCommitment,\n x_Overage = Overage,\n x_PurchasingBillingAccountId = PurchasingEnrollment,\n x_SkuOrderId = ReservationOrderId,\n x_SkuOrderName = ReservationOrderName,\n x_SkuSize = ArmSkuName,\n x_SkuTerm = isoMonths(Term),\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId = PurchasingSubscriptionGuid,\n x_TransactionType = EventType\n}\n\n// Transactions_final_v1_0 table\n.create-merge table Transactions_final_v1_0 (\n BilledCost: decimal, // MS CM EA+MCA 2023-05-01\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n ChargeCategory: string, // Hubs add-on\n ChargeClass: string, // Hubs add-on\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n PricingQuantity: decimal, // MS CM EA+MCA 2023-05-01\n PricingUnit: string, // Hubs add-on\n ProviderName: string, // Hubs add-on\n RegionId: string, // MS CM EA+MCA 2023-05-01\n RegionName: string, // MS CM EA+MCA 2023-05-01\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\n x_AccountName: string, // MS CM EA 2023-05-01\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\n x_CostCenter: string, // MS CM EA 2023-05-01\n x_InvoiceId: string, // MS CM MCA 2023-05-01\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\n x_IngestionTime: datetime, // Hubs add-on\n x_MonetaryCommitment: decimal, // MS CM EA 2023-05-01\n x_Overage: decimal, // MS CM EA 2023-05-01\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\n)\n\n// Update policy for Transactions_raw -> Transactions_final_v1_0 table\n.alter table Transactions_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Transactions_raw\",\n \"Query\": \"Transactions_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n", - "CONFIG": "config", - "HUB_DATA_EXPLORER": "hubDataExplorer", - "HUB_DB": "Hub", - "INGESTION": "ingestion", - "INGESTION_DB": "Ingestion", - "INGESTION_ID_SEPARATOR": "__", - "ftkReleaseUri": "[if(endsWith(variables('finOpsToolkitVersion'), '-dev'), 'https://github.com/microsoft/finops-toolkit/releases/latest/download', format('https://github.com/microsoft/finops-toolkit/releases/download/v{0}', variables('finOpsToolkitVersion')))]", - "useFabric": "[not(empty(parameters('fabricQueryUri')))]", - "useAzure": "[and(not(variables('useFabric')), not(empty(parameters('clusterName'))))]", - "dataExplorerPrivateDnsZoneName": "[replace(format('privatelink.{0}.{1}', parameters('app').hub.location, replace(environment().suffixes.storage, 'core', 'kusto')), '..', '.')]", - "ingestionCapacity": { - "Dev(No SLA)_Standard_E2a_v4": 1, - "Dev(No SLA)_Standard_D11_v2": 1, - "Standard_D11_v2": 2, - "Standard_D12_v2": 4, - "Standard_D13_v2": 8, - "Standard_D14_v2": 16, - "Standard_D16d_v5": 16, - "Standard_D32d_v4": 32, - "Standard_D32d_v5": 32, - "Standard_DS13_v2+1TB_PS": 8, - "Standard_DS13_v2+2TB_PS": 8, - "Standard_DS14_v2+3TB_PS": 16, - "Standard_DS14_v2+4TB_PS": 16, - "Standard_E2a_v4": 2, - "Standard_E2ads_v5": 2, - "Standard_E2d_v4": 2, - "Standard_E2d_v5": 2, - "Standard_E4a_v4": 4, - "Standard_E4ads_v5": 4, - "Standard_E4d_v4": 4, - "Standard_E4d_v5": 4, - "Standard_E8a_v4": 8, - "Standard_E8ads_v5": 8, - "Standard_E8as_v4+1TB_PS": 8, - "Standard_E8as_v4+2TB_PS": 8, - "Standard_E8as_v5+1TB_PS": 8, - "Standard_E8as_v5+2TB_PS": 8, - "Standard_E8d_v4": 8, - "Standard_E8d_v5": 8, - "Standard_E8s_v4+1TB_PS": 8, - "Standard_E8s_v4+2TB_PS": 8, - "Standard_E8s_v5+1TB_PS": 8, - "Standard_E8s_v5+2TB_PS": 8, - "Standard_E16a_v4": 16, - "Standard_E16ads_v5": 16, - "Standard_E16as_v4+3TB_PS": 16, - "Standard_E16as_v4+4TB_PS": 16, - "Standard_E16as_v5+3TB_PS": 16, - "Standard_E16as_v5+4TB_PS": 16, - "Standard_E16d_v4": 16, - "Standard_E16d_v5": 16, - "Standard_E16s_v4+3TB_PS": 16, - "Standard_E16s_v4+4TB_PS": 16, - "Standard_E16s_v5+3TB_PS": 16, - "Standard_E16s_v5+4TB_PS": 16, - "Standard_E64i_v3": 64, - "Standard_E80ids_v4": 80, - "Standard_EC8ads_v5": 8, - "Standard_EC8as_v5+1TB_PS": 8, - "Standard_EC8as_v5+2TB_PS": 8, - "Standard_EC16ads_v5": 16, - "Standard_EC16as_v5+3TB_PS": 16, - "Standard_EC16as_v5+4TB_PS": 16, - "Standard_L4s": 4, - "Standard_L8as_v3": 8, - "Standard_L8s": 8, - "Standard_L8s_v2": 8, - "Standard_L8s_v3": 8, - "Standard_L16as_v3": 16, - "Standard_L16s": 16, - "Standard_L16s_v2": 16, - "Standard_L16s_v3": 16, - "Standard_L32as_v3": 32, - "Standard_L32s_v3": 32 - }, - "dataExplorerIngestionCapacity": "[if(variables('useFabric'), parameters('fabricCapacityUnits'), if(not(variables('useAzure')), 1, coalesce(tryGet(variables('ingestionCapacity'), parameters('clusterSku')), 1)))]", - "dataExplorerUri": "[if(variables('useFabric'), parameters('fabricQueryUri'), format('https://{0}.{1}.kusto.windows.net', replace(parameters('clusterName'), '_', '-'), parameters('app').hub.location))]", - "finOpsToolkitVersion": "12.0" - }, - "resources": { - "cluster::adfClusterAdmin": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Kusto/clusters/principalAssignments", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), 'adf-mi-cluster-admin')]", - "properties": { - "principalType": "App", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[reference('dataFactory', '2018-06-01', 'full').identity.tenantId]", - "role": "AllDatabasesAdmin" - }, - "dependsOn": [ - "cluster", - "dataFactory" - ] - }, - "cluster::ingestionDb": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Kusto/clusters/databases", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), variables('INGESTION_DB'))]", - "location": "[parameters('app').hub.location]", - "kind": "ReadWrite", - "dependsOn": [ - "cluster" - ] - }, - "cluster::hubDb": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Kusto/clusters/databases", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), variables('HUB_DB'))]", - "location": "[parameters('app').hub.location]", - "kind": "ReadWrite", - "dependsOn": [ - "cluster" - ] - }, - "dataFactoryVNet::dataExplorerManagedPrivateEndpoint": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', variables('HUB_DATA_EXPLORER'))]", - "properties": { - "name": "[variables('HUB_DATA_EXPLORER')]", - "groupId": "cluster", - "privateLinkResourceId": "[resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-'))]", - "fqdns": [ - "[format('https://{0}.{1}.kusto.windows.net', replace(parameters('clusterName'), '_', '-'), parameters('app').hub.location)]" - ] - }, - "dependsOn": [ - "appRegistration", - "cluster" - ] - }, - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "dependsOn": [ - "appRegistration" - ] - }, - "blobPrivateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "dependsOn": [ - "appRegistration" - ] - }, - "queuePrivateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.queue.{0}', environment().suffixes.storage)]", - "dependsOn": [ - "appRegistration" - ] - }, - "tablePrivateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.table.{0}', environment().suffixes.storage)]", - "dependsOn": [ - "appRegistration" - ] - }, - "storage": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "dependsOn": [ - "appRegistration" - ] - }, - "cluster": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Kusto/clusters", - "apiVersion": "2023-08-15", - "name": "[replace(parameters('clusterName'), '_', '-')]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Kusto/clusters'), createObject()))]", - "sku": { - "name": "[parameters('clusterSku')]", - "tier": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 'Basic', 'Standard')]", - "capacity": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 1, if(equals(parameters('clusterCapacity'), 1), 2, parameters('clusterCapacity')))]" - }, - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "enableStreamingIngest": true, - "enableAutoStop": false, - "publicNetworkAccess": "[if(parameters('app').hub.options.privateRouting, 'Disabled', 'Enabled')]" - }, - "dependsOn": [ - "appRegistration" - ] - }, - "clusterStorageAccess": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(replace(parameters('clusterName'), '_', '-'), subscription().id, 'Storage Blob Data Contributor')]", - "properties": { - "description": "Give \"Storage Blob Data Contributor\" to the cluster", - "principalId": "[reference('cluster', '2023-08-15', 'full').identity.principalId]", - "principalType": "ServicePrincipal", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" - }, - "dependsOn": [ - "appRegistration", - "cluster" - ] - }, - "dataExplorerPrivateDnsZone": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[variables('dataExplorerPrivateDnsZoneName')]", - "location": "global", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateDnsZones'), createObject()))]", - "properties": {} - }, - "dataExplorerPrivateDnsZoneLink": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('dataExplorerPrivateDnsZoneName'), format('{0}-link', replace(variables('dataExplorerPrivateDnsZoneName'), '.', '-')))]", - "location": "global", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateDnsZones/virtualNetworkLinks'), createObject()))]", - "properties": { - "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "dataExplorerPrivateDnsZone" - ] - }, - "dataExplorerEndpoint": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', replace(parameters('clusterName'), '_', '-'))]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateEndpoints'), createObject()))]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.dataExplorer]" - }, - "privateLinkServiceConnections": [ - { - "name": "dataExplorerLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-'))]", - "groupIds": [ - "cluster" - ] - } - } - ] - }, - "dependsOn": [ - "cluster" - ] - }, - "dataExplorerPrivateDnsZoneGroup": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', replace(parameters('clusterName'), '_', '-')), 'dataExplorer-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "privatelink-westus-kusto-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" - } - }, - { - "name": "privatelink-blob-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - }, - { - "name": "privatelink-table-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.table.{0}', environment().suffixes.storage))]" - } - }, - { - "name": "privatelink-queue-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "appRegistration", - "dataExplorerEndpoint", - "dataExplorerPrivateDnsZone" - ] - }, - "dataFactoryVNet": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "dependsOn": [ - "appRegistration" - ] - }, - "linkedService_dataExplorer": { - "condition": "[or(variables('useAzure'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('HUB_DATA_EXPLORER'))]", - "properties": "[shallowMerge(createArray(createObject('type', 'AzureDataExplorer', 'parameters', createObject('database', createObject('type', 'String', 'defaultValue', variables('INGESTION_DB'))), 'typeProperties', createObject('endpoint', variables('dataExplorerUri'), 'database', '@{linkedService().database}', 'tenant', reference('dataFactory', '2018-06-01', 'full').identity.tenantId, 'servicePrincipalId', reference('dataFactory', '2018-06-01', 'full').identity.principalId)), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]", - "dependsOn": [ - "appRegistration", - "cluster", - "dataFactory" - ] - }, - "linkedService_ftkRepo": { - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ftkRepo')]", - "properties": "[shallowMerge(createArray(createObject('type', 'HttpServer', 'parameters', createObject('filePath', createObject('type', 'string')), 'typeProperties', createObject('url', '@concat(''https://gitapp.hub.com/microsoft/finops-toolkit/'', linkedService().filePath)', 'enableServerCertificateValidation', true(), 'authenticationType', 'Anonymous')), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]", - "dependsOn": [ - "appRegistration" - ] - }, - "dataset_dataExplorer": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('HUB_DATA_EXPLORER'))]", - "properties": { - "type": "AzureDataExplorerTable", - "linkedServiceName": { - "parameters": { - "database": "@dataset().database" - }, - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference" - }, - "parameters": { - "database": { - "type": "String", - "defaultValue": "[variables('INGESTION_DB')]" - }, - "table": { - "type": "String" - } - }, - "typeProperties": { - "table": { - "value": "@dataset().table", - "type": "Expression" - } - } - }, - "dependsOn": [ - "appRegistration", - "linkedService_dataExplorer" - ] - }, - "dataset_ftkReleaseFile": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ftkReleaseFile')]", - "properties": { - "linkedServiceName": { - "referenceName": "ftkRepo", - "type": "LinkedServiceReference" - }, - "parameters": { - "fileName": { - "type": "string" - }, - "version": { - "type": "string", - "defaultValue": "[variables('finOpsToolkitVersion')]" - } - }, - "annotations": [], - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "HttpServerLocation", - "relativeUrl": { - "value": "@concat('releases/download/v', dataset().version, '/', dataset().fileName)", - "type": "Expression" - } - }, - "columnDelimiter": ",", - "escapeChar": "\\", - "firstRowAsHeader": true, - "quoteChar": "\"" - }, - "schema": [] - }, - "dependsOn": [ - "appRegistration", - "linkedService_ftkRepo" - ] - }, - "pipeline_InitializeHub": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_InitializeHub', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference" - } - } - }, - { - "name": "Set Version", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "version", - "value": { - "value": "@activity('Get Config').output.firstRow.version", - "type": "Expression" - } - } - }, - { - "name": "Set Scopes", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "scopes", - "value": { - "value": "@string(activity('Get Config').output.firstRow.scopes)", - "type": "Expression" - } - } - }, - { - "name": "Set Retention", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "retention", - "value": { - "value": "@string(activity('Get Config').output.firstRow.retention)", - "type": "Expression" - } - } - }, - { - "name": "Until Capacity Is Available", - "type": "Until", - "dependsOn": [ - { - "activity": "Set Version", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Retention", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@equals(variables('tryAgain'), false)", - "type": "Expression" - }, - "activities": [ - { - "name": "Confirm Ingestion Capacity", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "If Has Capacity", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Confirm Ingestion Capacity", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", - "type": "Expression" - }, - "ifFalseActivities": [ - { - "name": "Wait for Ingestion", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 15 - } - }, - { - "name": "Try Again", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait for Ingestion", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": true - } - } - ], - "ifTrueActivities": [ - { - "name": "Set ingestion policy in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": { - "value": "[if(variables('useFabric'), format('.show database {0} policy managed_identity', variables('INGESTION_DB')), format('.alter-merge database {0} policy managed_identity \"[ {{ ''ObjectId'' : ''{1}'', ''AllowedUsages'' : ''NativeIngestion'' }}]\"', variables('INGESTION_DB'), reference('cluster', '2023-08-15', 'full').identity.principalId))]", - "type": "Expression" - }, - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Save Hub Settings in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Set ingestion policy in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": { - "value": "@concat('.append HubSettingsLog <| print version=\"', variables('version'), '\",scopes=dynamic(', variables('scopes'), '),retention=dynamic(', variables('retention'), ') | extend scopes = iff(isnull(scopes[0]), pack_array(scopes), scopes) | mv-apply scopeObj = scopes on (where isnotempty(scopeObj.scope) | summarize scopes = make_set(scopeObj.scope))')", - "type": "Expression" - }, - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Update PricingUnits in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Save Hub Settings in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace PricingUnits <| externaldata(x_PricingUnitDescription: string, AccountTypes: string, x_PricingBlockSize: decimal, PricingUnit: string)[@\"{0}/PricingUnits.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away AccountTypes', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Update Regions in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update PricingUnits in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace Regions <| externaldata(ResourceLocation: string, RegionId: string, RegionName: string)[@\"{0}/Regions.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Update ResourceTypes in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update Regions in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace ResourceTypes <| externaldata(x_ResourceType: string, SingularDisplayName: string, PluralDisplayName: string, LowerSingularDisplayName: string, LowerPluralDisplayName: string, IsPreview: bool, Description: string, IconUri: string, Links: string)[@\"{0}/ResourceTypes.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away Links', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Update Services in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update ResourceTypes in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace Services <| externaldata(x_ConsumedService: string, x_ResourceType: string, ServiceName: string, ServiceCategory: string, ServiceSubcategory: string, PublisherName: string, x_PublisherCategory: string, x_Environment: string, x_ServiceModel: string)[@\"{0}/Services.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Ingestion Complete", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Update Services in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - } - ] - } - }, - { - "name": "Abort On Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "If Has Capacity", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - } - ], - "timeout": "0.02:00:00" - } - }, - { - "name": "Timeout Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Until Capacity Is Available", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": "Data Explorer ingestion timed out after 2 hours while waiting for available capacity. Please re-run this pipeline to re-attempt ingestion. If you continue to see this error, please report an issue at https://aka.ms/ftk/ideas.", - "errorCode": "DataExplorerIngestionTimeout" - } - } - ], - "concurrency": 1, - "variables": { - "version": { - "type": "String" - }, - "scopes": { - "type": "String" - }, - "retention": { - "type": "String" - }, - "tryAgain": { - "type": "Boolean", - "defaultValue": true - } - } - }, - "dependsOn": [ - "appRegistration", - "cluster", - "linkedService_dataExplorer" - ], - "metadata": { - "description": "Initializes the hub instance based on the configuration settings." - } - }, - "pipeline_ToDataExplorer": { - "condition": "[or(variables('useAzure'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ETL_dataExplorer', variables('INGESTION')))]", - "properties": { - "activities": [ - { - "name": "Read Hub Config", - "description": "Read the hub config to determine how long data should be retained.", - "type": "Lookup", - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": "settings.json", - "folderPath": "[variables('CONFIG')]" - } - } - } - }, - { - "name": "Set Final Retention Months", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Hub Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "finalRetentionMonths", - "value": { - "value": "@coalesce(activity('Read Hub Config').output.firstRow.retention.final.months, 999)", - "type": "Expression" - } - } - }, - { - "name": "Until Capacity Is Available", - "type": "Until", - "dependsOn": [ - { - "activity": "Set Final Retention Months", - "dependencyConditions": [ - "Completed", - "Skipped" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@equals(variables('tryAgain'), false)", - "type": "Expression" - }, - "activities": [ - { - "name": "Confirm Ingestion Capacity", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference" - } - }, - { - "name": "If Has Capacity", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Confirm Ingestion Capacity", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", - "type": "Expression" - }, - "ifFalseActivities": [ - { - "name": "Wait for Ingestion", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 15 - } - }, - { - "name": "Try Again", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait for Ingestion", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": true - } - } - ], - "ifTrueActivities": [ - { - "name": "Pre-Ingest Cleanup", - "description": "Cost Management exports include all month-to-date data from the previous export run. To ensure data is not double-reported, it must be dropped from the raw table before ingestion completes. Remove previous ingestions into the raw table for the month and any previous runs of the current ingestion month file in any table.", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "typeProperties": { - "command": { - "value": "@concat('.drop extents <| .show extents | where (TableName == \"', pipeline().parameters.table, '\" and Tags !has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '\") or (Tags has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '/', pipeline().parameters.originalFileName, '\")')", - "type": "Expression" - }, - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Ingest Data", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Pre-Ingest Cleanup", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 3, - "retryIntervalInSeconds": 120, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": { - "value": "[format('@concat(''.ingest into table '', pipeline().parameters.table, '' (\"abfss://{0}@{1}.dfs.{2}/'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.fileName, '';{3}\") with (format=\"parquet\", ingestionMappingReference=\"'', pipeline().parameters.table, ''_mapping\", tags=\"[\\\"drop-by:'', pipeline().parameters.ingestionId, ''\\\", \\\"drop-by:'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.originalFileName, ''\\\", \\\"drop-by:ftk-version-{4}\\\"]\"); print Success = assert(iff(toscalar($command_results | project-keep HasErrors) == false, true, false), \"Ingestion Failed\")'')', variables('INGESTION'), parameters('app').storage, environment().suffixes.storage, if(variables('useFabric'), 'impersonate', 'managed_identity=system'), variables('finOpsToolkitVersion'))]", - "type": "Expression" - }, - "commandTimeout": "01:00:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Post-Ingest Cleanup", - "description": "Cost Management exports include all month-to-date data from the previous export run. To ensure data is not double-reported, it must be dropped after ingestion completes. Remove the current ingestion month file from raw and any old ingestions for the month from the final table.", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Ingest Data", - "dependencyConditions": [ - "Completed" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "typeProperties": { - "command": { - "value": "@concat('.drop extents <| .show extents | extend isOldFinalData = (TableName startswith \"', replace(pipeline().parameters.table, '_raw', '_final_v'), '\" and Tags !has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '\") | extend isPastFinalRetention = (TableName startswith \"', replace(pipeline().parameters.table, '_raw', '_final_v'), '\" and todatetime(substring(strcat(replace_string(extract(\"drop-by:[A-Za-z]+/(\\\\d{4}/\\\\d{2}(/\\\\d{2})?)\", 1, Tags), \"/\", \"-\"), \"-01\"), 0, 10)) < datetime_add(\"month\", -', if(lessOrEquals(variables('finalRetentionMonths'), 0), 0, variables('finalRetentionMonths')), ', startofmonth(now()))) | where isOldFinalData or isPastFinalRetention')", - "type": "Expression" - }, - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Ingestion Complete", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Post-Ingest Cleanup", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - }, - { - "name": "Abort On Ingestion Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Ingest Data", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - }, - { - "name": "Ingestion Failed Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Abort On Ingestion Error", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Data Explorer ingestion into the ', pipeline().parameters.table, ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Ingest Data').output.errors), 0), activity('Ingest Data').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Ingest Data').output.errors), 0), activity('Ingest Data').output.errors[0].Code, 'None'), ')')", - "type": "Expression" - }, - "errorCode": "DataExplorerIngestionFailed" - } - }, - { - "name": "Abort On Pre-Ingest Drop Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Pre-Ingest Cleanup", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - }, - { - "name": "Pre-Ingest Drop Failed Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Abort On Pre-Ingest Drop Error", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Data Explorer pre-ingestion cleanup (drop extents from raw table) for the ', pipeline().parameters.table, ' table failed. Ingestion was not completed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", - "type": "Expression" - }, - "errorCode": "DataExplorerPreIngestionDropFailed" - } - }, - { - "name": "Abort On Post-Ingest Drop Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Post-Ingest Cleanup", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - }, - { - "name": "Post-Ingest Drop Failed Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Abort On Post-Ingest Drop Error", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Data Explorer post-ingestion cleanup (drop extents from final tables) for the ', replace(pipeline().parameters.table, '_raw', '_final_*'), ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", - "type": "Expression" - }, - "errorCode": "DataExplorerPostIngestionDropFailed" - } - } - ] - } - } - ], - "timeout": "0.02:00:00" + "filesUploaded": { + "type": "int", + "metadata": { + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" + }, + "identityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + } + } + } + }, + "dependsOn": [ + "appRegistration" + ] + } + }, + "outputs": { + "exportContainer": { + "type": "string", + "metadata": { + "description": "Name of the container used for Cost Management exports." + }, + "value": "[reference('exportContainer').outputs.containerName.value]" + }, + "schemaFilesUploaded": { + "type": "int", + "metadata": { + "description": "Number of schema files uploaded." + }, + "value": "[reference('schemaFiles').outputs.filesUploaded.value]" + } + } + } + }, + "dependsOn": [ + "core" + ] + }, + "dataExplorer": { + "condition": "[variables('deployDataExplorer')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "dataExplorer", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('dataExplorerName')]" + }, + "clusterSku": { + "value": "[parameters('dataExplorerSku')]" + }, + "clusterCapacity": { + "value": "[parameters('dataExplorerCapacity')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('hub').tags]" + }, + "tagsByResource": { + "value": "[parameters('tagsByResource')]" + }, + "dataFactoryName": { + "value": "[reference('core').outputs.dataFactoryName.value]" + }, + "rawRetentionInDays": { + "value": "[parameters('dataExplorerRawRetentionInDays')]" + }, + "virtualNetworkId": "[if(parameters('enablePublicAccess'), createObject('value', ''), createObject('value', reference('infrastructure').outputs.vNetId.value))]", + "privateEndpointSubnetId": "[if(parameters('enablePublicAccess'), createObject('value', ''), createObject('value', reference('infrastructure').outputs.dataExplorerSubnetId.value))]", + "enablePublicAccess": { + "value": "[parameters('enablePublicAccess')]" + }, + "storageAccountName": { + "value": "[reference('core').outputs.storageAccountName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "12711851392414163333" + } + }, + "parameters": { + "clusterName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics. If empty, Azure Data Explorer will not be deployed. Required to use with Power BI if you have more than $2-5M/mo in costs being monitored. Default: \"\" (do not use)." + } + }, + "clusterSku": { + "type": "string", + "defaultValue": "Dev(No SLA)_Standard_E2a_v4", + "allowedValues": [ + "Dev(No SLA)_Standard_E2a_v4", + "Dev(No SLA)_Standard_D11_v2", + "Standard_D11_v2", + "Standard_D12_v2", + "Standard_D13_v2", + "Standard_D14_v2", + "Standard_D16d_v5", + "Standard_D32d_v4", + "Standard_D32d_v5", + "Standard_DS13_v2+1TB_PS", + "Standard_DS13_v2+2TB_PS", + "Standard_DS14_v2+3TB_PS", + "Standard_DS14_v2+4TB_PS", + "Standard_E2a_v4", + "Standard_E2ads_v5", + "Standard_E2d_v4", + "Standard_E2d_v5", + "Standard_E4a_v4", + "Standard_E4ads_v5", + "Standard_E4d_v4", + "Standard_E4d_v5", + "Standard_E8a_v4", + "Standard_E8ads_v5", + "Standard_E8as_v4+1TB_PS", + "Standard_E8as_v4+2TB_PS", + "Standard_E8as_v5+1TB_PS", + "Standard_E8as_v5+2TB_PS", + "Standard_E8d_v4", + "Standard_E8d_v5", + "Standard_E8s_v4+1TB_PS", + "Standard_E8s_v4+2TB_PS", + "Standard_E8s_v5+1TB_PS", + "Standard_E8s_v5+2TB_PS", + "Standard_E16a_v4", + "Standard_E16ads_v5", + "Standard_E16as_v4+3TB_PS", + "Standard_E16as_v4+4TB_PS", + "Standard_E16as_v5+3TB_PS", + "Standard_E16as_v5+4TB_PS", + "Standard_E16d_v4", + "Standard_E16d_v5", + "Standard_E16s_v4+3TB_PS", + "Standard_E16s_v4+4TB_PS", + "Standard_E16s_v5+3TB_PS", + "Standard_E16s_v5+4TB_PS", + "Standard_E64i_v3", + "Standard_E80ids_v4", + "Standard_EC8ads_v5", + "Standard_EC8as_v5+1TB_PS", + "Standard_EC8as_v5+2TB_PS", + "Standard_EC16ads_v5", + "Standard_EC16as_v5+3TB_PS", + "Standard_EC16as_v5+4TB_PS", + "Standard_L4s", + "Standard_L8as_v3", + "Standard_L8s", + "Standard_L8s_v2", + "Standard_L8s_v3", + "Standard_L16as_v3", + "Standard_L16s", + "Standard_L16s_v2", + "Standard_L16s_v3", + "Standard_L32as_v3", + "Standard_L32s_v3" + ], + "metadata": { + "description": "Optional. Name of the Azure Data Explorer SKU. Default: \"Dev(No SLA)_Standard_E2a_v4\"." + } + }, + "clusterCapacity": { + "type": "int", + "defaultValue": 1, + "minValue": 1, + "maxValue": 1000, + "metadata": { + "description": "Optional. Number of nodes to use in the cluster. Allowed values: 1 for the Basic SKU tier and 2-1000 for Standard. Default: 1 for dev/test SKUs, 2 for standard SKUs." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Azure location to use for the managed identity and deployment script to auto-start triggers. Default: (resource group location)." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to all resources." + } + }, + "tagsByResource": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." + } + }, + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory instance." + } + }, + "rawRetentionInDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account to use for data ingestion." + } + }, + "virtualNetworkId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the virtual network for private endpoints." + } + }, + "privateEndpointSubnetId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet for private endpoints." + } + }, + "enablePublicAccess": { + "type": "bool", + "metadata": { + "description": "Optional. Enable public access." + } + } + }, + "variables": { + "$fxv#0": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_1(id: string) {\r\n dynamic({\r\n \"arizeai.observabilityeval/organizations\": { \"SingularDisplayName\": \"Azure Native Arize AI Cloud Service\" }\r\n ,\"astronomer.astro/organizations\": { \"SingularDisplayName\": \"Astro Organization\" }\r\n ,\"citrix.services/xenappessentials\": { \"SingularDisplayName\": \"Citrix Virtual Apps Essentials\" }\r\n ,\"citrix.services/xendesktopessentials\": { \"SingularDisplayName\": \"Citrix Virtual Desktops Essentials\" }\r\n ,\"commvault.contentstore/cloudaccounts\": { \"SingularDisplayName\": \"Commvault Cloud Account\" }\r\n ,\"commvault.contentstore/cloudaccounts/plans\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts plan\" }\r\n ,\"commvault.contentstore/cloudaccounts/protectiongroups\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection group\" }\r\n ,\"commvault.contentstore/cloudaccounts/protectiongroups/protecteditems\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection groups protected item\" }\r\n ,\"commvault.contentstore/cloudaccounts/storages\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts storage\" }\r\n ,\"dell.storage/filesystems\": { \"SingularDisplayName\": \"Dell PowerScale\" }\r\n ,\"dynatrace.observability/monitors\": { \"SingularDisplayName\": \"Dynatrace\" }\r\n ,\"github.network/networksettings\": { \"SingularDisplayName\": \"GitHub.Network network setting\" }\r\n ,\"informatica.datamanagement/organizations\": { \"SingularDisplayName\": \"Informatica Organization\" }\r\n ,\"lambdatest.hyperexecute/organizations\": { \"SingularDisplayName\": \"Azure Native LambdaTest - HyperExecute Cloud Service\" }\r\n ,\"microsoft.aad/domainservices\": { \"SingularDisplayName\": \"Microsoft Entra Domain Services\" }\r\n ,\"microsoft.aadiam/diagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.aadiam diagnostic setting\" }\r\n ,\"microsoft.aadiam/privatelinkforazuread\": { \"SingularDisplayName\": \"Private Link for Microsoft Entra ID\" }\r\n ,\"microsoft.advisor/advisorscore\": { \"SingularDisplayName\": \"Microsoft.Advisor advisor score\" }\r\n ,\"microsoft.advisor/assessments\": { \"SingularDisplayName\": \"Microsoft.Advisor assessment\" }\r\n ,\"microsoft.advisor/configurations\": { \"SingularDisplayName\": \"Microsoft.Advisor configuration\" }\r\n ,\"microsoft.advisor/generaterecommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor generate recommendation\" }\r\n ,\"microsoft.advisor/metadata\": { \"SingularDisplayName\": \"Microsoft.Advisor metadata\" }\r\n ,\"microsoft.advisor/recommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendation\" }\r\n ,\"microsoft.advisor/recommendations/suppressions\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendations suppression\" }\r\n ,\"microsoft.advisor/resiliencyreviews\": { \"SingularDisplayName\": \"Microsoft.Advisor resiliency review\" }\r\n ,\"microsoft.agfoodplatform/farmbeats\": { \"SingularDisplayName\": \"Azure Data Manager for Agriculture\" }\r\n ,\"microsoft.agfoodplatform/farmbeatsextensiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats extension definition\" }\r\n ,\"microsoft.agfoodplatform/farmbeatssolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats solution definition\" }\r\n ,\"microsoft.agricultureplatform/agriservices\": { \"SingularDisplayName\": \"Agriculture data solutions\" }\r\n ,\"microsoft.akshybrid/agentpools\": { \"SingularDisplayName\": \"Microsoft.AksHybrid agent pool\" }\r\n ,\"microsoft.akshybrid/provisionedclusters\": { \"SingularDisplayName\": \"Microsoft.AksHybrid provisioned cluster\" }\r\n ,\"microsoft.akshybrid/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.AksHybrid upgrade profile\" }\r\n ,\"microsoft.alertsmanagement/actionrules\": { \"SingularDisplayName\": \"Alert processing rule\" }\r\n ,\"microsoft.alertsmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alert\" }\r\n ,\"microsoft.alertsmanagement/alerts/enrichments\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alerts enrichment\" }\r\n ,\"microsoft.alertsmanagement/prometheusrulegroups\": { \"SingularDisplayName\": \"Prometheus rule group\" }\r\n ,\"microsoft.alertsmanagement/smartdetectoralertrules\": { \"SingularDisplayName\": \"Smart detector alert rule\" }\r\n ,\"microsoft.alertsmanagement/smartgroups\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement smart group\" }\r\n ,\"microsoft.alertsmanagement/tenantactivitylogalerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement tenant activity log alert\" }\r\n ,\"microsoft.all/arcvirtualmachines\": { \"SingularDisplayName\": \"Azure Arc virtual machine\" }\r\n ,\"microsoft.all/hcivirtualmachines\": { \"SingularDisplayName\": \"Azure Local Virtual Machine - Azure Arc\" }\r\n ,\"microsoft.all/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.analysisservices/servers\": { \"SingularDisplayName\": \"Analysis Services server\" }\r\n ,\"microsoft.anybuild/clusters\": { \"SingularDisplayName\": \"AnyBuild cluster\" }\r\n ,\"microsoft.apicenter/deletedservices\": { \"SingularDisplayName\": \"Microsoft.ApiCenter deleted service\" }\r\n ,\"microsoft.apicenter/services\": { \"SingularDisplayName\": \"API Center\" }\r\n ,\"microsoft.apicenter/services/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.apimanagement/gateways\": { \"SingularDisplayName\": \"API Management gateway\" }\r\n ,\"microsoft.apimanagement/gateways/configconnections\": { \"SingularDisplayName\": \"Microsoft.ApiManagement gateways config connection\" }\r\n ,\"microsoft.apimanagement/service\": { \"SingularDisplayName\": \"API Management service\" }\r\n ,\"microsoft.apimanagement/service/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.apisecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.ApiSecurity defender setting\" }\r\n ,\"microsoft.app/agents\": { \"SingularDisplayName\": \"SRE Agent\" }\r\n ,\"microsoft.app/builders\": { \"SingularDisplayName\": \"Microsoft.App builder\" }\r\n ,\"microsoft.app/builders/builds\": { \"SingularDisplayName\": \"Microsoft.App builders build\" }\r\n ,\"microsoft.app/connectedenvironments\": { \"SingularDisplayName\": \"Container Apps Connected Environment\" }\r\n ,\"microsoft.app/containerapps\": { \"SingularDisplayName\": \"Container App\" }\r\n ,\"microsoft.app/jobs\": { \"SingularDisplayName\": \"Container App Job\" }\r\n ,\"microsoft.app/logicapps\": { \"SingularDisplayName\": \"Logic app\" }\r\n ,\"microsoft.app/logicapps/workflows\": { \"SingularDisplayName\": \"Logic app workflow\" }\r\n ,\"microsoft.app/managedenvironments\": { \"SingularDisplayName\": \"Container Apps Environment\" }\r\n ,\"microsoft.app/sessionpools\": { \"SingularDisplayName\": \"Container App Session Pool\" }\r\n ,\"microsoft.app/spaces\": { \"SingularDisplayName\": \"App Space\" }\r\n ,\"microsoft.appassessment/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate project\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessment\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedapplications\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed application\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed machine\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/machinestoassess\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments machines to asses\" }\r\n ,\"microsoft.appassessment/migrateprojects/sites\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects site\" }\r\n ,\"microsoft.appassessment/migrateprojects/sites/applianceconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects sites appliance configuration\" }\r\n ,\"microsoft.appcomplianceautomation/reports\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation report\" }\r\n ,\"microsoft.appcomplianceautomation/reports/evidences\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports evidence\" }\r\n ,\"microsoft.appcomplianceautomation/reports/scopingconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports scoping configuration\" }\r\n ,\"microsoft.appcomplianceautomation/reports/snapshots\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshot\" }\r\n ,\"microsoft.appcomplianceautomation/reports/snapshots/controls\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshots control\" }\r\n ,\"microsoft.appcomplianceautomation/reports/webhooks\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports webhook\" }\r\n ,\"microsoft.appconfiguration/configurationstores\": { \"SingularDisplayName\": \"App Configuration\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hub\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs/applications\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs application\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs/applications/members\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs applications member\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsite\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites agent\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqldatabases\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqldatabase\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqlinstances\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqlinstance\" }\r\n ,\"microsoft.appplatform/spring\": { \"SingularDisplayName\": \"Azure Spring Apps\" }\r\n ,\"microsoft.appsecurity/appprotectmanagedrulesetmanifests\": { \"SingularDisplayName\": \"Microsoft.AppSecurity app protect managed rule set manifest\" }\r\n ,\"microsoft.appsecurity/policies\": { \"SingularDisplayName\": \"App Protect Policy\" }\r\n ,\"microsoft.arc/all\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\r\n ,\"microsoft.arc/allfairfax\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\r\n ,\"microsoft.arc/kubernetesresources\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\r\n ,\"microsoft.arc/kubernetesresourcesfairfax\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\r\n ,\"microsoft.arcnetworking/arcnwloadbalancers\": { \"SingularDisplayName\": \"Microsoft.ArcNetworking arc nw load balancer\" }\r\n ,\"microsoft.aszlabhardware/labservers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware labserver\" }\r\n ,\"microsoft.aszlabhardware/reservations\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservation\" }\r\n ,\"microsoft.aszlabhardware/reservations/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservations server\" }\r\n ,\"microsoft.aszlabhardware/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware server\" }\r\n ,\"microsoft.attestation/attestationproviders\": { \"SingularDisplayName\": \"Attestation provider\" }\r\n ,\"microsoft.authorization/accessreviewhistorydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review history definition\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definition\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instance\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances/decisions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instances decision\" }\r\n ,\"microsoft.authorization/accessreviewschedulesettings\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule setting\" }\r\n ,\"microsoft.authorization/datapolicymanifests\": { \"SingularDisplayName\": \"Microsoft.Authorization data policy manifest\" }\r\n ,\"microsoft.authorization/denyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization deny assignment\" }\r\n ,\"microsoft.authorization/locks\": { \"SingularDisplayName\": \"Microsoft.Authorization lock\" }\r\n ,\"microsoft.authorization/policyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization policy assignment\" }\r\n ,\"microsoft.authorization/policydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definition\" }\r\n ,\"microsoft.authorization/policydefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definitions version\" }\r\n ,\"microsoft.authorization/policyexemptions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy exemption\" }\r\n ,\"microsoft.authorization/policysetdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definition\" }\r\n ,\"microsoft.authorization/policysetdefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definitions version\" }\r\n ,\"microsoft.authorization/privatelinkassociations\": { \"SingularDisplayName\": \"Microsoft.Authorization private link association\" }\r\n ,\"microsoft.authorization/provideroperations\": { \"SingularDisplayName\": \"Microsoft.Authorization provider operation\" }\r\n ,\"microsoft.authorization/resourcemanagementprivatelinks\": { \"SingularDisplayName\": \"Resource management private link\" }\r\n ,\"microsoft.authorization/roleassignmentapprovals\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approval\" }\r\n ,\"microsoft.authorization/roleassignmentapprovals/stages\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approvals stage\" }\r\n ,\"microsoft.authorization/roleassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment\" }\r\n ,\"microsoft.authorization/roleassignmentscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule instance\" }\r\n ,\"microsoft.authorization/roleassignmentschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule request\" }\r\n ,\"microsoft.authorization/roleassignmentschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule\" }\r\n ,\"microsoft.authorization/roledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role definition\" }\r\n ,\"microsoft.authorization/roleeligibilityscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule instance\" }\r\n ,\"microsoft.authorization/roleeligibilityschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule request\" }\r\n ,\"microsoft.authorization/roleeligibilityschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule\" }\r\n ,\"microsoft.authorization/rolemanagementalertconfigurations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert configuration\" }\r\n ,\"microsoft.authorization/rolemanagementalertdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert definition\" }\r\n ,\"microsoft.authorization/rolemanagementalertoperations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert operation\" }\r\n ,\"microsoft.authorization/rolemanagementalerts\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert\" }\r\n ,\"microsoft.authorization/rolemanagementalerts/alertincidents\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alerts alert incident\" }\r\n ,\"microsoft.authorization/rolemanagementpolicies\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy\" }\r\n ,\"microsoft.authorization/rolemanagementpolicyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy assignment\" }\r\n ,\"microsoft.automanage/bestpractices\": { \"SingularDisplayName\": \"Microsoft.Automanage best practice\" }\r\n ,\"microsoft.automanage/bestpractices/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage best practices version\" }\r\n ,\"microsoft.automanage/configurationprofileassignments\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignment\" }\r\n ,\"microsoft.automanage/configurationprofileassignments/reports\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignments report\" }\r\n ,\"microsoft.automanage/configurationprofiles\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile\" }\r\n ,\"microsoft.automanage/configurationprofiles/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profiles version\" }\r\n ,\"microsoft.automanage/serviceprincipals\": { \"SingularDisplayName\": \"ServicePrincipals\" }\r\n ,\"microsoft.automation/automationaccounts\": { \"SingularDisplayName\": \"Automation account\" }\r\n ,\"microsoft.automation/automationaccounts/hybridrunbookworkergroups\": { \"SingularDisplayName\": \"Automation hybrid worker group\" }\r\n ,\"microsoft.automation/automationaccounts/runbooks\": { \"SingularDisplayName\": \"Automation runbook\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform account\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/accounts/datapools\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform accounts data pool\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform workspace\" }\r\n ,\"microsoft.avs/privateclouds\": { \"SingularDisplayName\": \"Azure VMware Solution private cloud\" }\r\n ,\"microsoft.awsconnector/accessanalyzeranalyzers\": { \"SingularDisplayName\": \"Access Analyzer Analyzer\" }\r\n ,\"microsoft.awsconnector/acmcertificatesummaries\": { \"SingularDisplayName\": \"ACM Certificate Summary\" }\r\n ,\"microsoft.awsconnector/apigatewayrestapis\": { \"SingularDisplayName\": \"Api Gateway Rest Api\" }\r\n ,\"microsoft.awsconnector/apigatewaystages\": { \"SingularDisplayName\": \"Api Gateway Stage\" }\r\n ,\"microsoft.awsconnector/applicationautoscalingscalabletargets\": { \"SingularDisplayName\": \"Application Auto Scaling Scalable Target\" }\r\n ,\"microsoft.awsconnector/appsyncgraphqlapis\": { \"SingularDisplayName\": \"App Sync Graphql Api\" }\r\n ,\"microsoft.awsconnector/autoscalingautoscalinggroups\": { \"SingularDisplayName\": \"Auto Scaling Auto Scaling Group\" }\r\n ,\"microsoft.awsconnector/cloudformationstacks\": { \"SingularDisplayName\": \"Cloud Formation Stack\" }\r\n ,\"microsoft.awsconnector/cloudformationstacksets\": { \"SingularDisplayName\": \"Cloud Formation Stack Set\" }\r\n ,\"microsoft.awsconnector/cloudfrontdistributions\": { \"SingularDisplayName\": \"Cloud Front Distribution\" }\r\n ,\"microsoft.awsconnector/cloudtrailtrails\": { \"SingularDisplayName\": \"Cloud Trail Trail\" }\r\n ,\"microsoft.awsconnector/cloudwatchalarms\": { \"SingularDisplayName\": \"Cloud Watch Alarm\" }\r\n ,\"microsoft.awsconnector/codebuildprojects\": { \"SingularDisplayName\": \"Code Build Project\" }\r\n ,\"microsoft.awsconnector/codebuildsourcecredentialsinfos\": { \"SingularDisplayName\": \"Code Build Source Credentials Info\" }\r\n ,\"microsoft.awsconnector/configserviceconfigurationrecorders\": { \"SingularDisplayName\": \"Config Service Configuration Recorder\" }\r\n ,\"microsoft.awsconnector/configserviceconfigurationrecorderstatuses\": { \"SingularDisplayName\": \"Config Service Configuration Recorder Status\" }\r\n ,\"microsoft.awsconnector/configservicedeliverychannels\": { \"SingularDisplayName\": \"Config Service Delivery Channel\" }\r\n ,\"microsoft.awsconnector/databasemigrationservicereplicationinstances\": { \"SingularDisplayName\": \"Database Migration Service Replication Instance\" }\r\n ,\"microsoft.awsconnector/daxclusters\": { \"SingularDisplayName\": \"DAX Cluster\" }\r\n ,\"microsoft.awsconnector/dynamodbcontinuousbackupsdescriptions\": { \"SingularDisplayName\": \"Dynamo DB Continuous Backups Description\" }\r\n ,\"microsoft.awsconnector/dynamodbtables\": { \"SingularDisplayName\": \"Dynamo DB Table\" }\r\n ,\"microsoft.awsconnector/ec2accountattributes\": { \"SingularDisplayName\": \"EC2 Account Attribute\" }\r\n ,\"microsoft.awsconnector/ec2addresses\": { \"SingularDisplayName\": \"EC2 Address\" }\r\n ,\"microsoft.awsconnector/ec2flowlogs\": { \"SingularDisplayName\": \"EC2 Flow Log\" }\r\n ,\"microsoft.awsconnector/ec2images\": { \"SingularDisplayName\": \"EC2 Image\" }\r\n ,\"microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\r\n ,\"microsoft.awsconnector/ec2instancestatuses\": { \"SingularDisplayName\": \"EC2 Instance Status\" }\r\n ,\"microsoft.awsconnector/ec2ipams\": { \"SingularDisplayName\": \"EC2 Ipam\" }\r\n ,\"microsoft.awsconnector/ec2keypairs\": { \"SingularDisplayName\": \"EC2 Key Pair\" }\r\n ,\"microsoft.awsconnector/ec2networkacls\": { \"SingularDisplayName\": \"EC2 Network Acl\" }\r\n ,\"microsoft.awsconnector/ec2networkinterfaces\": { \"SingularDisplayName\": \"EC2 Network Interface\" }\r\n ,\"microsoft.awsconnector/ec2routetables\": { \"SingularDisplayName\": \"EC2 Route Table\" }\r\n ,\"microsoft.awsconnector/ec2securitygroups\": { \"SingularDisplayName\": \"EC2 Security Group\" }\r\n ,\"microsoft.awsconnector/ec2snapshots\": { \"SingularDisplayName\": \"EC2 Snapshot\" }\r\n ,\"microsoft.awsconnector/ec2subnets\": { \"SingularDisplayName\": \"EC2 Subnet\" }\r\n ,\"microsoft.awsconnector/ec2volumes\": { \"SingularDisplayName\": \"EC2 Volume\" }\r\n ,\"microsoft.awsconnector/ec2vpcendpoints\": { \"SingularDisplayName\": \"EC2 VPCEndpoint\" }\r\n ,\"microsoft.awsconnector/ec2vpcpeeringconnections\": { \"SingularDisplayName\": \"EC2 VPCPeering Connection\" }\r\n ,\"microsoft.awsconnector/ec2vpcs\": { \"SingularDisplayName\": \"EC2 VPC\" }\r\n ,\"microsoft.awsconnector/ecrimagedetails\": { \"SingularDisplayName\": \"ECR Image Detail\" }\r\n ,\"microsoft.awsconnector/ecrrepositories\": { \"SingularDisplayName\": \"ECR Repository\" }\r\n ,\"microsoft.awsconnector/ecsclusters\": { \"SingularDisplayName\": \"ECS Cluster\" }\r\n ,\"microsoft.awsconnector/ecsservices\": { \"SingularDisplayName\": \"ECS Service\" }\r\n ,\"microsoft.awsconnector/ecstaskdefinitions\": { \"SingularDisplayName\": \"ECS Task Definition\" }\r\n ,\"microsoft.awsconnector/efsfilesystems\": { \"SingularDisplayName\": \"EFS File System\" }\r\n ,\"microsoft.awsconnector/efsmounttargets\": { \"SingularDisplayName\": \"EFS Mount Target\" }\r\n ,\"microsoft.awsconnector/eksnodegroups\": { \"SingularDisplayName\": \"EKS Nodegroup\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkapplications\": { \"SingularDisplayName\": \"Elastic Beanstalk Application\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkconfigurationtemplates\": { \"SingularDisplayName\": \"Elastic Beanstalk Configuration Template\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkenvironments\": { \"SingularDisplayName\": \"Elastic Beanstalk Environment\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2listeners\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Listener\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2loadbalancers\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Load Balancer\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2targetgroups\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Target Group\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2targethealthdescriptions\": { \"SingularDisplayName\": \"Elastic Load Balancing v2 Target Health Description\" }\r\n ,\"microsoft.awsconnector/elasticsearchdomains\": { \"SingularDisplayName\": \"Elasticsearch Domain\" }\r\n ,\"microsoft.awsconnector/emrclusters\": { \"SingularDisplayName\": \"EMR Cluster\" }\r\n ,\"microsoft.awsconnector/guarddutydetectors\": { \"SingularDisplayName\": \"Guard Duty Detector\" }\r\n ,\"microsoft.awsconnector/iamaccesskeylastuseds\": { \"SingularDisplayName\": \"IAM Access Key Last Used\" }\r\n ,\"microsoft.awsconnector/iamaccesskeymetadata\": { \"SingularDisplayName\": \"IAM Access Key Metadata\" }\r\n ,\"microsoft.awsconnector/iamgroups\": { \"SingularDisplayName\": \"IAM Group\" }\r\n ,\"microsoft.awsconnector/iaminstanceprofiles\": { \"SingularDisplayName\": \"IAM Instance Profile\" }\r\n ,\"microsoft.awsconnector/iammanagedpolicies\": { \"SingularDisplayName\": \"IAM Managed Policy\" }\r\n ,\"microsoft.awsconnector/iammfadevices\": { \"SingularDisplayName\": \"IAM MFADevice\" }\r\n ,\"microsoft.awsconnector/iampasswordpolicies\": { \"SingularDisplayName\": \"IAM Password Policy\" }\r\n ,\"microsoft.awsconnector/iampolicyversions\": { \"SingularDisplayName\": \"IAM Policy Version\" }\r\n ,\"microsoft.awsconnector/iamroles\": { \"SingularDisplayName\": \"IAM Role\" }\r\n ,\"microsoft.awsconnector/iamservercertificates\": { \"SingularDisplayName\": \"IAM Server Certificate\" }\r\n ,\"microsoft.awsconnector/iamuserpolicies\": { \"SingularDisplayName\": \"IAM User Policy\" }\r\n ,\"microsoft.awsconnector/iamvirtualmfadevices\": { \"SingularDisplayName\": \"IAM Virtual MFADevice\" }\r\n ,\"microsoft.awsconnector/kmsaliases\": { \"SingularDisplayName\": \"KMS Alias\" }\r\n ,\"microsoft.awsconnector/kmskeys\": { \"SingularDisplayName\": \"KMS Key\" }\r\n ,\"microsoft.awsconnector/lambdafunctioncodelocations\": { \"SingularDisplayName\": \"Lambda Function Code Location\" }\r\n ,\"microsoft.awsconnector/lambdafunctionconfigurations\": { \"SingularDisplayName\": \"Microsoft.AwsConnector lambda function configuration\" }\r\n ,\"microsoft.awsconnector/lambdafunctions\": { \"SingularDisplayName\": \"Lambda Function\" }\r\n ,\"microsoft.awsconnector/licensemanagerlicenses\": { \"SingularDisplayName\": \"License Manager License\" }\r\n ,\"microsoft.awsconnector/lightsailbuckets\": { \"SingularDisplayName\": \"Lightsail Bucket\" }\r\n ,\"microsoft.awsconnector/lightsailinstances\": { \"SingularDisplayName\": \"Lightsail Instance\" }\r\n ,\"microsoft.awsconnector/logsloggroups\": { \"SingularDisplayName\": \"Logs Log Group\" }\r\n ,\"microsoft.awsconnector/logslogstreams\": { \"SingularDisplayName\": \"Logs Log Stream\" }\r\n ,\"microsoft.awsconnector/logsmetricfilters\": { \"SingularDisplayName\": \"Logs Metric Filter\" }\r\n ,\"microsoft.awsconnector/logssubscriptionfilters\": { \"SingularDisplayName\": \"Logs Subscription Filter\" }\r\n ,\"microsoft.awsconnector/macie2jobsummaries\": { \"SingularDisplayName\": \"Macie2 Job Summary\" }\r\n ,\"microsoft.awsconnector/macieallowlists\": { \"SingularDisplayName\": \"Macie Allow List\" }\r\n ,\"microsoft.awsconnector/networkfirewallfirewallpolicies\": { \"SingularDisplayName\": \"Network Firewall Firewall Policy\" }\r\n ,\"microsoft.awsconnector/networkfirewallfirewalls\": { \"SingularDisplayName\": \"Network Firewall Firewall\" }\r\n ,\"microsoft.awsconnector/networkfirewallrulegroups\": { \"SingularDisplayName\": \"Network Firewall Rule Group\" }\r\n ,\"microsoft.awsconnector/opensearchdomainstatuses\": { \"SingularDisplayName\": \"Open Search Domain Status\" }\r\n ,\"microsoft.awsconnector/opensearchservicedomains\": { \"SingularDisplayName\": \"Open Search Service Domain\" }\r\n ,\"microsoft.awsconnector/organizationsaccounts\": { \"SingularDisplayName\": \"Organizations Account\" }\r\n ,\"microsoft.awsconnector/organizationsorganizations\": { \"SingularDisplayName\": \"Organizations Organization\" }\r\n ,\"microsoft.awsconnector/rdsdbclusters\": { \"SingularDisplayName\": \"RDS DBCluster\" }\r\n ,\"microsoft.awsconnector/rdsdbinstances\": { \"SingularDisplayName\": \"RDS DBInstance\" }\r\n ,\"microsoft.awsconnector/rdsdbsnapshotattributesresults\": { \"SingularDisplayName\": \"RDS DBSnapshot Attributes Result\" }\r\n ,\"microsoft.awsconnector/rdsdbsnapshots\": { \"SingularDisplayName\": \"RDS DBSnapshot\" }\r\n ,\"microsoft.awsconnector/rdseventsubscriptions\": { \"SingularDisplayName\": \"RDS Event Subscription\" }\r\n ,\"microsoft.awsconnector/rdsexporttasks\": { \"SingularDisplayName\": \"RDS Export Task\" }\r\n ,\"microsoft.awsconnector/redshiftclusterparametergroups\": { \"SingularDisplayName\": \"Redshift Cluster Parameter Group\" }\r\n ,\"microsoft.awsconnector/redshiftclusters\": { \"SingularDisplayName\": \"Redshift Cluster\" }\r\n ,\"microsoft.awsconnector/route53domainsdomainsummaries\": { \"SingularDisplayName\": \"Route 53 Domains Domain Summary\" }\r\n ,\"microsoft.awsconnector/route53hostedzones\": { \"SingularDisplayName\": \"Route53 Hosted Zone\" }\r\n ,\"microsoft.awsconnector/route53resourcerecordsets\": { \"SingularDisplayName\": \"Route 53 Resource Record Set\" }\r\n ,\"microsoft.awsconnector/s3accesscontrolpolicies\": { \"SingularDisplayName\": \"S3 Access Control Policy\" }\r\n ,\"microsoft.awsconnector/s3accesspoints\": { \"SingularDisplayName\": \"S3 Access Point\" }\r\n ,\"microsoft.awsconnector/s3bucketpolicies\": { \"SingularDisplayName\": \"S3 Bucket Policy\" }\r\n ,\"microsoft.awsconnector/s3buckets\": { \"SingularDisplayName\": \"S3 Bucket\" }\r\n ,\"microsoft.awsconnector/s3controlmultiregionaccesspointpolicydocuments\": { \"SingularDisplayName\": \"S3 Control Multi Region Access Point Policy Document\" }\r\n ,\"microsoft.awsconnector/sagemakerapps\": { \"SingularDisplayName\": \"Sage Maker App\" }\r\n ,\"microsoft.awsconnector/sagemakerdevices\": { \"SingularDisplayName\": \"Sage Maker Device\" }\r\n ,\"microsoft.awsconnector/sagemakerimages\": { \"SingularDisplayName\": \"Sage Maker Image\" }\r\n ,\"microsoft.awsconnector/sagemakernotebookinstancesummaries\": { \"SingularDisplayName\": \"Sage Maker Notebook Instance Summary\" }\r\n ,\"microsoft.awsconnector/secretsmanagerresourcepolicies\": { \"SingularDisplayName\": \"Secrets Manager Resource Policy\" }\r\n ,\"microsoft.awsconnector/secretsmanagersecrets\": { \"SingularDisplayName\": \"Secrets Manager Secret\" }\r\n ,\"microsoft.awsconnector/snssubscriptions\": { \"SingularDisplayName\": \"SNS Subscription\" }\r\n ,\"microsoft.awsconnector/snstopics\": { \"SingularDisplayName\": \"SNS Topic\" }\r\n ,\"microsoft.awsconnector/sqsqueues\": { \"SingularDisplayName\": \"SQS Queue\" }\r\n ,\"microsoft.awsconnector/ssminstanceinformations\": { \"SingularDisplayName\": \"SSM Instance Information\" }\r\n ,\"microsoft.awsconnector/ssmparameters\": { \"SingularDisplayName\": \"SSM Parameter\" }\r\n ,\"microsoft.awsconnector/ssmresourcecompliancesummaryitems\": { \"SingularDisplayName\": \"SSM Resource Compliance Summary Item\" }\r\n ,\"microsoft.awsconnector/wafv2ipsets\": { \"SingularDisplayName\": \"WAFv2 IPSet\" }\r\n ,\"microsoft.awsconnector/wafv2loggingconfigurations\": { \"SingularDisplayName\": \"WAFv2 Logging Configuration\" }\r\n ,\"microsoft.awsconnector/wafv2webaclassociations\": { \"SingularDisplayName\": \"WAFv2 Web ACLAssociation\" }\r\n ,\"microsoft.awsconnector/wafwebaclsummaries\": { \"SingularDisplayName\": \"WAF Web ACLSummary\" }\r\n ,\"microsoft.azureactivedirectory/b2cdirectories\": { \"SingularDisplayName\": \"B2C tenant\" }\r\n ,\"microsoft.azureactivedirectory/ciamdirectories\": { \"SingularDisplayName\": \"External Configuration Tenant\" }\r\n ,\"microsoft.azureactivedirectory/guestusages\": { \"SingularDisplayName\": \"Guest Usage\" }\r\n ,\"microsoft.azurearcdata/datacontrollers\": { \"SingularDisplayName\": \"Azure Arc data controller\" }\r\n ,\"microsoft.azurearcdata/mysqlserver\": { \"SingularDisplayName\": \"MySql Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/postgresinstances\": { \"SingularDisplayName\": \"PostgreSQL server ? Azure Arc\" }\r\n ,\"microsoft.azurearcdata/postgressqlserver\": { \"SingularDisplayName\": \"PostgresSql Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlmanagedinstances\": { \"SingularDisplayName\": \"SQL managed instance - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserveresulicenses\": { \"SingularDisplayName\": \"SQL Server ESU license\" }\r\n ,\"microsoft.azurearcdata/sqlserverinstances\": { \"SingularDisplayName\": \"SQL Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserverinstances/databases\": { \"SingularDisplayName\": \"SQL Server database - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserverlicenses\": { \"SingularDisplayName\": \"SQL Server License\" }\r\n ,\"microsoft.azurebusinesscontinuity/deletedunifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity deleted unified protected item\" }\r\n ,\"microsoft.azurebusinesscontinuity/unifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity unified protected item\" }\r\n ,\"microsoft.azurecis/aadapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis AAD application\" }\r\n ,\"microsoft.azurecis/addressrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis address record\" }\r\n ,\"microsoft.azurecis/autopilotenvironments\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot environment\" }\r\n ,\"microsoft.azurecis/autopilotmachinefunctions\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot machine function\" }\r\n ,\"microsoft.azurecis/autopilotsoftwareloadbalancevirtualips\": { \"SingularDisplayName\": \"Microsoft.AzureCis auto pilot software load balance virtual IP\" }\r\n ,\"microsoft.azurecis/azcopies\": { \"SingularDisplayName\": \"Microsoft.AzureCis az copy\" }\r\n ,\"microsoft.azurecis/canonicalnamerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis canonical name record\" }\r\n ,\"microsoft.azurecis/dsmsallowlists\": { \"SingularDisplayName\": \"Microsoft.AzureCis ds msallowlist\" }\r\n ,\"microsoft.azurecis/dsmscertificates\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms certificate\" }\r\n ,\"microsoft.azurecis/dsmsrootfolders\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms root folder\" }\r\n ,\"microsoft.azurecis/dstsapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts application\" }\r\n ,\"microsoft.azurecis/dstsserviceaccounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service account\" }\r\n ,\"microsoft.azurecis/dstsserviceclientidentities\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service client identity\" }\r\n ,\"microsoft.azurecis/genericgenevaactions\": { \"SingularDisplayName\": \"Microsoft.AzureCis generic geneva action\" }\r\n ,\"microsoft.azurecis/plannedquotas\": { \"SingularDisplayName\": \"Microsoft.AzureCis planned quota\" }\r\n ,\"microsoft.azurecis/pointerrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis pointer record\" }\r\n ,\"microsoft.azurecis/publishconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis publish config value\" }\r\n ,\"microsoft.azurecis/pushagentv2accounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis push agent v2 account\" }\r\n ,\"microsoft.azurecis/servicerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis service record\" }\r\n ,\"microsoft.azurecis/sharedconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis shared config value\" }\r\n ,\"microsoft.azurecloudmetadata/clouds\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata cloud\" }\r\n ,\"microsoft.azurecloudmetadata/clouds/geographies\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geography\" }\r\n ,\"microsoft.azurecloudmetadata/clouds/geographies/regions\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geographies region\" }\r\n ,\"microsoft.azuredatatransfer/connections\": { \"SingularDisplayName\": \"Connection\" }\r\n ,\"microsoft.azuredatatransfer/connections/flows\": { \"SingularDisplayName\": \"Flow\" }\r\n ,\"microsoft.azuredatatransfer/pipelines\": { \"SingularDisplayName\": \"Pipeline\" }\r\n ,\"microsoft.azurefleet/fleets\": { \"SingularDisplayName\": \"Compute Fleet\" }\r\n ,\"microsoft.azurefleet/fleetscomputehub\": { \"SingularDisplayName\": \"Compute Fleet\" }\r\n ,\"microsoft.azureimagetestingforlinux/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job\" }\r\n ,\"microsoft.azureimagetestingforlinux/jobtemplates\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job template\" }\r\n ,\"microsoft.azurelargeinstance/azurelargeinstances\": { \"SingularDisplayName\": \"Azure Large Instance\" }\r\n ,\"microsoft.azurelargeinstance/azurelargestorageinstances\": { \"SingularDisplayName\": \"Microsoft.AzureLargeInstance Azure large storage instance\" }\r\n ,\"microsoft.azurepercept/accounts\": { \"SingularDisplayName\": \"Microsoft.AzurePercept account\" }\r\n ,\"microsoft.azurepercept/accounts/devices\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts device\" }\r\n ,\"microsoft.azurepercept/accounts/devices/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts devices sensor\" }\r\n ,\"microsoft.azurepercept/accounts/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts sensor\" }\r\n ,\"microsoft.azurepercept/accounts/solutioninstances\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solutioninstance\" }\r\n ,\"microsoft.azurepercept/accounts/solutions\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solution\" }\r\n ,\"microsoft.azurepercept/accounts/targets\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts target\" }\r\n ,\"microsoft.azureplaywrightservice/accounts\": { \"SingularDisplayName\": \"Playwright Testing\" }\r\n ,\"microsoft.azurescan/scanningaccounts\": { \"SingularDisplayName\": \"ESRP Scan\" }\r\n ,\"microsoft.azuresphere/catalogs\": { \"SingularDisplayName\": \"Azure Sphere Catalog\" }\r\n ,\"microsoft.azurespherev2/catalogs\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalog\" }\r\n ,\"microsoft.azurespherev2/catalogs/artifacts\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs artifact\" }\r\n ,\"microsoft.azurespherev2/catalogs/certificates\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs certificate\" }\r\n ,\"microsoft.azurespherev2/catalogs/deviceregistrations\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs device registration\" }\r\n ,\"microsoft.azurespherev2/catalogs/provisioningpackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs provisioning package\" }\r\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channel\" }\r\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels/deployments\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channels deployment\" }\r\n ,\"microsoft.azurespherev2/catalogs/updatepackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs update package\" }\r\n ,\"microsoft.azurestack/cloudmanifestfiles\": { \"SingularDisplayName\": \"Microsoft.AzureStack cloud manifest file\" }\r\n ,\"microsoft.azurestack/linkedsubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack linked subscription\" }\r\n ,\"microsoft.azurestack/registrations\": { \"SingularDisplayName\": \"Microsoft.AzureStack registration\" }\r\n ,\"microsoft.azurestack/registrations/customersubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations customer subscription\" }\r\n ,\"microsoft.azurestack/registrations/products\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations product\" }\r\n ,\"microsoft.azurestackhci/clusters\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/clusters/updates/updateruns\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/clusters/updatesummaries\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/devicepools\": { \"SingularDisplayName\": \"Azure Stack\" }\r\n ,\"microsoft.azurestackhci/edgedevices\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge device\" }\r\n ,\"microsoft.azurestackhci/edgedevices/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge devices job\" }\r\n ,\"microsoft.azurestackhci/edgemachines\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machine\" }\r\n ,\"microsoft.azurestackhci/edgemachines/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machines job\" }\r\n ,\"microsoft.azurestackhci/edgenodepools\": { \"SingularDisplayName\": \"Azure Stack\" }\r\n ,\"microsoft.azurestackhci/galleryimages\": { \"SingularDisplayName\": \"Azure Local Gallery image\" }\r\n ,\"microsoft.azurestackhci/logicalnetworks\": { \"SingularDisplayName\": \"Azure Local Logical network\" }\r\n ,\"microsoft.azurestackhci/marketplacegalleryimages\": { \"SingularDisplayName\": \"Azure Local Marketplace Gallery image\" }\r\n ,\"microsoft.azurestackhci/networkinterfaces\": { \"SingularDisplayName\": \"Azure Local VM Network Interface\" }\r\n ,\"microsoft.azurestackhci/networksecuritygroups\": { \"SingularDisplayName\": \"Azure Local Network Security Group\" }\r\n ,\"microsoft.azurestackhci/networksecuritygroups/securityrules\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI network security groups security rule\" }\r\n ,\"microsoft.azurestackhci/storagecontainers\": { \"SingularDisplayName\": \"Azure Local Storage path\" }\r\n ,\"microsoft.azurestackhci/virtualharddisks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual hard disk\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instance\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances guest agent\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.azurestackhci/virtualmachines\": { \"SingularDisplayName\": \"Azure Local virtual machine - Azure Arc\" }\r\n ,\"microsoft.azurestackhci/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual network\" }\r\n ,\"microsoft.backupsolutions/vmwareapplications\": { \"SingularDisplayName\": \"Microsoft.BackupSolutions vmware application\" }\r\n ,\"microsoft.bakeryhybrid/pies\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid py\" }\r\n ,\"microsoft.bakeryhybrid/pies/nestedresourcetype\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid pies nested resource type\" }\r\n ,\"microsoft.baremetal/baremetalconnections\": { \"SingularDisplayName\": \"Microsoft.BareMetal bare metal connection\" }\r\n ,\"microsoft.baremetal/crayservers\": { \"SingularDisplayName\": \"Cray Server\" }\r\n ,\"microsoft.baremetal/monitoringservers\": { \"SingularDisplayName\": \"Monitoring Server\" }\r\n ,\"microsoft.baremetal/peeringsettings\": { \"SingularDisplayName\": \"Microsoft.BareMetal peering setting\" }\r\n ,\"microsoft.baremetalinfrastructure/baremetalinstances\": { \"SingularDisplayName\": \"BareMetal Instance\" }\r\n ,\"microsoft.baremetalinfrastructure/baremetalstorageinstances\": { \"SingularDisplayName\": \"Microsoft.BareMetalInfrastructure bare metal storage instance\" }\r\n ,\"microsoft.batch/batchaccounts\": { \"SingularDisplayName\": \"Batch account\" }\r\n ,\"microsoft.billing/billingaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing account\" }\r\n ,\"microsoft.billing/billingaccounts/agreements\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts agreement\" }\r\n ,\"microsoft.billing/billingaccounts/associatedtenants\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts associated tenant\" }\r\n ,\"microsoft.billing/billingaccounts/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts available balance\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profile\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles available balance\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers transfer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/instructions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles instruction\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice section\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections product\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections transfer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/paymentmethodlinks\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles payment method link\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles policy\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/transactions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles transaction\" }\r\n ,\"microsoft.billing/billingaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptionaliases\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription aliase\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptions/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscriptions invoice\" }\r\n ,\"microsoft.billing/billingaccounts/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customer\" }\r\n ,\"microsoft.billing/billingaccounts/customers/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/customers/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers policy\" }\r\n ,\"microsoft.billing/billingaccounts/customers/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers product\" }\r\n ,\"microsoft.billing/billingaccounts/departments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts department\" }\r\n ,\"microsoft.billing/billingaccounts/departments/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/departments/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/departments/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments enrollment account\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment account\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\r\n ,\"microsoft.billing/billingaccounts/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\r\n ,\"microsoft.billing/billingaccounts/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice section\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections product\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections transfer\" }\r\n ,\"microsoft.billing/billingaccounts/lineofcredit\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts line of credit\" }\r\n ,\"microsoft.billing/billingaccounts/migrations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts migration\" }\r\n ,\"microsoft.billing/billingaccounts/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts payment method\" }\r\n ,\"microsoft.billing/billingaccounts/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts policy\" }\r\n ,\"microsoft.billing/billingaccounts/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts product\" }\r\n ,\"microsoft.billing/billingaccounts/reservationorders\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation order\" }\r\n ,\"microsoft.billing/billingaccounts/reservationorders/reservations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation orders reservation\" }\r\n ,\"microsoft.billing/billingaccounts/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\r\n ,\"microsoft.billing/billingaccounts/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\r\n ,\"microsoft.billing/billingperiods\": { \"SingularDisplayName\": \"Microsoft.Billing billing period\" }\r\n ,\"microsoft.billing/billingproperty\": { \"SingularDisplayName\": \"Microsoft.Billing billing property\" }\r\n ,\"microsoft.billing/billingrequests\": { \"SingularDisplayName\": \"Microsoft.Billing billing request\" }\r\n ,\"microsoft.billing/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing role assignment\" }\r\n ,\"microsoft.billing/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing role definition\" }\r\n ,\"microsoft.billing/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing enrollment account\" }\r\n ,\"microsoft.billing/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing payment method\" }\r\n ,\"microsoft.billing/policies\": { \"SingularDisplayName\": \"Microsoft.Billing policy\" }\r\n ,\"microsoft.billing/promotions\": { \"SingularDisplayName\": \"Microsoft.Billing promotion\" }\r\n ,\"microsoft.billing/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing transfer\" }\r\n ,\"microsoft.billingbenefits/credits\": { \"SingularDisplayName\": \"Credit\" }\r\n ,\"microsoft.billingbenefits/discounts\": { \"SingularDisplayName\": \"Discount\" }\r\n ,\"microsoft.billingbenefits/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\r\n ,\"microsoft.billingbenefits/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\r\n ,\"microsoft.billingbenefits/maccs\": { \"SingularDisplayName\": \"Microsoft Azure Consumption Commitment\" }\r\n ,\"microsoft.billingbenefits/reservationorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits reservation order aliase\" }\r\n ,\"microsoft.billingbenefits/savingsplanorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits savings plan order aliase\" }\r\n ,\"microsoft.billingbenefits/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\r\n ,\"microsoft.billingbenefits/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\r\n ,\"microsoft.bing/accounts\": { \"SingularDisplayName\": \"Bing Resource\" }\r\n ,\"microsoft.blockchain/blockchainmembers\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain member\" }\r\n ,\"microsoft.blockchain/blockchainmembers/transactionnodes\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain members transaction node\" }\r\n ,\"microsoft.blockchaintokens/tokenservices\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token service\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/blockchainnetworks\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services blockchain network\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/groups\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services group\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/groups/accounts\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services groups account\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/tokentemplates\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services token template\" }\r\n ,\"microsoft.bluefin/instances\": { \"SingularDisplayName\": \"Microsoft.Bluefin instance\" }\r\n ,\"microsoft.bluefin/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances dataset\" }\r\n ,\"microsoft.bluefin/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances pipeline\" }\r\n ,\"microsoft.blueprint/blueprintassignments\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint assignment\" }\r\n ,\"microsoft.blueprint/blueprints\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint\" }\r\n ,\"microsoft.blueprint/blueprints/artifacts\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints artifact\" }\r\n ,\"microsoft.blueprint/blueprints/versions\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints version\" }\r\n ,\"microsoft.botservice/botservices\": { \"SingularDisplayName\": \"Bot Service\" }\r\n ,\"microsoft.cache/redis\": { \"SingularDisplayName\": \"Redis cache\" }\r\n ,\"microsoft.cache/redisenterprise\": { \"SingularDisplayName\": \"Azure Managed Redis\" }\r\n ,\"microsoft.cache/redisenterprise/databases\": { \"SingularDisplayName\": \"Redis Enterprise database\" }\r\n ,\"microsoft.capacity/reservationorders\": { \"SingularDisplayName\": \"Reservation order\" }\r\n ,\"microsoft.capacity/reservationorders/reservations\": { \"SingularDisplayName\": \"Reservation\" }\r\n ,\"microsoft.cascade/sites\": { \"SingularDisplayName\": \"Microsoft.Cascade site\" }\r\n ,\"microsoft.cdn/cdnwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Content Delivery Network WAF policy\" }\r\n ,\"microsoft.cdn/edgeactions\": { \"SingularDisplayName\": \"Edge Action\" }\r\n ,\"microsoft.cdn/profiles\": { \"SingularDisplayName\": \"Front Door and CDN profile\" }\r\n ,\"microsoft.cdn/profiles/afdendpoints\": { \"SingularDisplayName\": \"Endpoint\" }\r\n ,\"microsoft.cdn/profiles/afdendpoints/routes\": { \"SingularDisplayName\": \"Route\" }\r\n ,\"microsoft.cdn/profiles/customdomains\": { \"SingularDisplayName\": \"Custom domain\" }\r\n ,\"microsoft.cdn/profiles/endpoints\": { \"SingularDisplayName\": \"CDN endpoint\" }\r\n ,\"microsoft.cdn/profiles/endpoints/customdomains\": { \"SingularDisplayName\": \"CDN custom domain\" }\r\n ,\"microsoft.cdn/profiles/endpoints/origins\": { \"SingularDisplayName\": \"CDN origin\" }\r\n ,\"microsoft.cdn/profiles/origingroups\": { \"SingularDisplayName\": \"Origin group\" }\r\n ,\"microsoft.cdn/profiles/origingroups/origins\": { \"SingularDisplayName\": \"Origin\" }\r\n ,\"microsoft.cdn/profiles/rulesets\": { \"SingularDisplayName\": \"Rule set\" }\r\n ,\"microsoft.cdn/profiles/rulesets/rules\": { \"SingularDisplayName\": \"Rule\" }\r\n ,\"microsoft.cdn/profiles/secrets\": { \"SingularDisplayName\": \"Secret\" }\r\n ,\"microsoft.cdn/profiles/securitypolicies\": { \"SingularDisplayName\": \"Security policy\" }\r\n ,\"microsoft.certificateregistration/certificateorders\": { \"SingularDisplayName\": \"App Service certificate\" }\r\n ,\"microsoft.certify/testsuites\": { \"SingularDisplayName\": \"Microsoft.Certify test suite\" }\r\n ,\"microsoft.certify/validationjobs\": { \"SingularDisplayName\": \"Microsoft.Certify validation job\" }\r\n ,\"microsoft.changeanalysis/profile\": { \"SingularDisplayName\": \"Microsoft.ChangeAnalysis profile\" }\r\n ,\"microsoft.changesafety/changestates\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change state\" }\r\n ,\"microsoft.changesafety/changestates/stageprogressions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change states stage progression\" }\r\n ,\"microsoft.changesafety/stagemaps\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety stage map\" }\r\n ,\"microsoft.changesafety/validations\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validation\" }\r\n ,\"microsoft.changesafety/validators\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validator\" }\r\n ,\"microsoft.changesafety/validators/versions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validators version\" }\r\n ,\"microsoft.chaos/experiments\": { \"SingularDisplayName\": \"Chaos Experiment\" }\r\n ,\"microsoft.chaos/privateaccesses\": { \"SingularDisplayName\": \"Agent Private Access\" }\r\n ,\"microsoft.chaos/targets\": { \"SingularDisplayName\": \"Microsoft.Chaos target\" }\r\n ,\"microsoft.chaos/targets/capabilities\": { \"SingularDisplayName\": \"Microsoft.Chaos targets capability\" }\r\n ,\"microsoft.classiccompute/domainnames\": { \"SingularDisplayName\": \"Cloud service (classic)\" }\r\n ,\"microsoft.classiccompute/domainnames/slots/roles\": { \"SingularDisplayName\": \"Cloud service role (classic)\" }\r\n ,\"microsoft.classiccompute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine (classic)\" }\r\n ,\"microsoft.classicnetwork/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group (classic)\" }\r\n ,\"microsoft.classicnetwork/reservedips\": { \"SingularDisplayName\": \"Reserved IP address (classic)\" }\r\n ,\"microsoft.classicnetwork/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network (classic)\" }\r\n })[tolower(id)]\r\n}\r\n", + "$fxv#1": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_2(id: string) {\r\n dynamic({\r\n \"microsoft.classicstorage/storageaccounts\": { \"SingularDisplayName\": \"Storage account (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/disks\": { \"SingularDisplayName\": \"Disk (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/osimages\": { \"SingularDisplayName\": \"OS image (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/vmimages\": { \"SingularDisplayName\": \"VM image (classic)\" }\r\n ,\"microsoft.cleanroom/cleanrooms\": { \"SingularDisplayName\": \"Microsoft.CleanRoom cleanroom\" }\r\n ,\"microsoft.cleanroom/collaborations\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaboration\" }\r\n ,\"microsoft.cleanroom/collaborations/contracts\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaborations contract\" }\r\n ,\"microsoft.cleanroom/consortiums\": { \"SingularDisplayName\": \"Microsoft.CleanRoom consortium\" }\r\n ,\"microsoft.cleanroom/microservices\": { \"SingularDisplayName\": \"Microsoft.CleanRoom microservice\" }\r\n ,\"microsoft.cloud/hubs\": { \"SingularDisplayName\": \"FinOps hub\" }\r\n ,\"microsoft.clouddeviceplatform/delegatedidentities\": { \"SingularDisplayName\": \"Microsoft.CloudDevicePlatform delegated identity\" }\r\n ,\"microsoft.cloudhealth/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\r\n ,\"microsoft.cloudtest/accounts\": { \"SingularDisplayName\": \"CloudTest Account\" }\r\n ,\"microsoft.cloudtest/buildcaches\": { \"SingularDisplayName\": \"1ES Build Cache\" }\r\n ,\"microsoft.cloudtest/hostedpools\": { \"SingularDisplayName\": \"1ES Hosted Pool\" }\r\n ,\"microsoft.cloudtest/images\": { \"SingularDisplayName\": \"1ES Image\" }\r\n ,\"microsoft.cloudtest/pools\": { \"SingularDisplayName\": \"CloudTest Pool\" }\r\n ,\"microsoft.clusterstor/nodes\": { \"SingularDisplayName\": \"ClusterStor\" }\r\n ,\"microsoft.codesigning/codesigningaccounts\": { \"SingularDisplayName\": \"Trusted Signing Account\" }\r\n ,\"microsoft.codespaces/plans\": { \"SingularDisplayName\": \"Microsoft.Codespaces plan\" }\r\n ,\"microsoft.cognitiveservices/accounts\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.cognitiveservices/accounts/projects\": { \"SingularDisplayName\": \"Azure AI Foundry project\" }\r\n ,\"microsoft.cognitiveservices/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plan\" }\r\n ,\"microsoft.cognitiveservices/commitmentplans/accountassociations\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plans account association\" }\r\n ,\"microsoft.communication/communicationservices\": { \"SingularDisplayName\": \"Communication Service\" }\r\n ,\"microsoft.communication/emailservices\": { \"SingularDisplayName\": \"Email Communication Service\" }\r\n ,\"microsoft.communication/emailservices/domains\": { \"SingularDisplayName\": \"Email Communication Services Domain\" }\r\n ,\"microsoft.community/communitytrainings\": { \"SingularDisplayName\": \"Community Training\" }\r\n ,\"microsoft.compositesolutions/compositesolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution definition\" }\r\n ,\"microsoft.compositesolutions/compositesolutions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution\" }\r\n ,\"microsoft.compute/availabilitysets\": { \"SingularDisplayName\": \"Availability set\" }\r\n ,\"microsoft.compute/capacityreservationgroups\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\r\n ,\"microsoft.compute/capacityreservationgroups/capacityreservations\": { \"SingularDisplayName\": \"Capacity reservation\" }\r\n ,\"microsoft.compute/capacityreservationgroupscomputehub\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\r\n ,\"microsoft.compute/cloudservices\": { \"SingularDisplayName\": \"Cloud service (extended support)\" }\r\n ,\"microsoft.compute/computefleetinstances\": { \"SingularDisplayName\": \"Instance\" }\r\n ,\"microsoft.compute/computefleetscalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.compute/diskaccesses\": { \"SingularDisplayName\": \"Disk Access\" }\r\n ,\"microsoft.compute/diskencryptionsets\": { \"SingularDisplayName\": \"Disk Encryption Set\" }\r\n ,\"microsoft.compute/disks\": { \"SingularDisplayName\": \"Disk\" }\r\n ,\"microsoft.compute/galleries\": { \"SingularDisplayName\": \"Azure compute gallery\" }\r\n ,\"microsoft.compute/galleries/applications\": { \"SingularDisplayName\": \"VM application definition\" }\r\n ,\"microsoft.compute/galleries/applications/versions\": { \"SingularDisplayName\": \"VM application version\" }\r\n ,\"microsoft.compute/galleries/images\": { \"SingularDisplayName\": \"VM image definition\" }\r\n ,\"microsoft.compute/galleries/images/versions\": { \"SingularDisplayName\": \"VM image version\" }\r\n ,\"microsoft.compute/galleries/imagescomputehub\": { \"SingularDisplayName\": \"VM image definition\" }\r\n ,\"microsoft.compute/hostgroups\": { \"SingularDisplayName\": \"Host group\" }\r\n ,\"microsoft.compute/hostgroups/hosts\": { \"SingularDisplayName\": \"Host\" }\r\n ,\"microsoft.compute/hostgroupscomputehub\": { \"SingularDisplayName\": \"Host group\" }\r\n ,\"microsoft.compute/images\": { \"SingularDisplayName\": \"Image\" }\r\n ,\"microsoft.compute/imagescomputehub\": { \"SingularDisplayName\": \"Image\" }\r\n ,\"microsoft.compute/locations/communitygalleries/images\": { \"SingularDisplayName\": \"Community image\" }\r\n ,\"microsoft.compute/locations/communitygalleries/imagescomputehub\": { \"SingularDisplayName\": \"Community image\" }\r\n ,\"microsoft.compute/proximityplacementgroups\": { \"SingularDisplayName\": \"Proximity placement group\" }\r\n ,\"microsoft.compute/proximityplacementgroupscomputehub\": { \"SingularDisplayName\": \"Proximity placement group\" }\r\n ,\"microsoft.compute/restorepointcollections\": { \"SingularDisplayName\": \"Restore Point Collection\" }\r\n ,\"microsoft.compute/restorepointcollections/restorepoints\": { \"SingularDisplayName\": \"Restore Point\" }\r\n ,\"microsoft.compute/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\r\n ,\"microsoft.compute/sshpublickeys\": { \"SingularDisplayName\": \"SSH key\" }\r\n ,\"microsoft.compute/standbypoolinstance\": { \"SingularDisplayName\": \"Standby pool\" }\r\n ,\"microsoft.compute/virtualmachinecomputehub\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.compute/virtualmachineflexinstances\": { \"SingularDisplayName\": \"Instance\" }\r\n ,\"microsoft.compute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.compute/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.compute/virtualmachinescalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine scale set instance\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines/networkinterfaces/ipconfigurations/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\r\n ,\"microsoft.compute/virtualmachinescalesetscomputehub\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.computehub/advisorcost\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisoroperationalexcellence\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorperformance\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorreliability\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorsecurity\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/all\": { \"SingularDisplayName\": \"All resources\" }\r\n ,\"microsoft.computehub/backup\": { \"SingularDisplayName\": \"Backup job\" }\r\n ,\"microsoft.computehub/computehubmain\": { \"SingularDisplayName\": \"Compute infrastructure\" }\r\n ,\"microsoft.computehub/healthevents\": { \"SingularDisplayName\": \"Health events\" }\r\n ,\"microsoft.computehub/linuxostype\": { \"SingularDisplayName\": \"Linux OS\" }\r\n ,\"microsoft.computehub/microsoftdefenderfreetrialsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\r\n ,\"microsoft.computehub/microsoftdefenderstandardsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\r\n ,\"microsoft.computehub/outages\": { \"SingularDisplayName\": \"Outages\" }\r\n ,\"microsoft.computehub/powerstatedeallocated\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/powerstaterunning\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/powerstatestopped\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/provisioningstatefailedresources\": { \"SingularDisplayName\": \"Provisioning states\" }\r\n ,\"microsoft.computehub/provisioningstatesucceededresources\": { \"SingularDisplayName\": \"Provisioning states\" }\r\n ,\"microsoft.computehub/windowsostype\": { \"SingularDisplayName\": \"Windows OS\" }\r\n ,\"microsoft.computeschedule/autoactions\": { \"SingularDisplayName\": \"Automatic Action\" }\r\n ,\"microsoft.computeschedule/autoactions/occurrences\": { \"SingularDisplayName\": \"Microsoft.ComputeSchedule auto actions occurrence\" }\r\n ,\"microsoft.confidentialledger/ledgers\": { \"SingularDisplayName\": \"Confidential Ledger\" }\r\n ,\"microsoft.confidentialledger/managedccfs\": { \"SingularDisplayName\": \"Managed CCF App\" }\r\n ,\"microsoft.confluent/agreements\": { \"SingularDisplayName\": \"Microsoft.Confluent agreement\" }\r\n ,\"microsoft.confluent/organizations\": { \"SingularDisplayName\": \"Confluent organization\" }\r\n ,\"microsoft.connectedcache/cachenodes\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\r\n ,\"microsoft.connectedcache/enterprisecustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\r\n ,\"microsoft.connectedcache/enterprisemcccustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\r\n ,\"microsoft.connectedcache/enterprisemcccustomers/enterprisemcccachenodes\": { \"SingularDisplayName\": \"MCC CacheNode for Enterprise\" }\r\n ,\"microsoft.connectedcache/ispcustomers\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\r\n ,\"microsoft.connectedcredentials/credentials\": { \"SingularDisplayName\": \"Microsoft.ConnectedCredentials credential\" }\r\n ,\"microsoft.connectedvehicle/platformaccounts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVehicle platform account\" }\r\n ,\"microsoft.connectedvmwarevsphere/clusters\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere cluster\" }\r\n ,\"microsoft.connectedvmwarevsphere/datastores\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere datastore\" }\r\n ,\"microsoft.connectedvmwarevsphere/hosts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere host\" }\r\n ,\"microsoft.connectedvmwarevsphere/resourcepools\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere resource pool\" }\r\n ,\"microsoft.connectedvmwarevsphere/vcenters\": { \"SingularDisplayName\": \"VMware vCenter\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instance\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances guest agent\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachines\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine template\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual network\" }\r\n ,\"microsoft.consumption/budgets\": { \"SingularDisplayName\": \"Microsoft.Consumption budget\" }\r\n ,\"microsoft.consumption/credits\": { \"SingularDisplayName\": \"Microsoft.Consumption credit\" }\r\n ,\"microsoft.consumption/pricesheets\": { \"SingularDisplayName\": \"Microsoft.Consumption pricesheet\" }\r\n ,\"microsoft.containerinstance/containergroupprofiles\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profile\" }\r\n ,\"microsoft.containerinstance/containergroupprofiles/revisions\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profiles revision\" }\r\n ,\"microsoft.containerinstance/containergroups\": { \"SingularDisplayName\": \"Container instances\" }\r\n ,\"microsoft.containerinstance/ngroups\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance ngroup\" }\r\n ,\"microsoft.containerregistry/registries\": { \"SingularDisplayName\": \"Container registry\" }\r\n ,\"microsoft.containerregistry/registries/replications\": { \"SingularDisplayName\": \"Container registry replication\" }\r\n ,\"microsoft.containerregistry/registries/scopemaps\": { \"SingularDisplayName\": \"Container registry scope map\" }\r\n ,\"microsoft.containerregistry/registries/tokens\": { \"SingularDisplayName\": \"Container registry token\" }\r\n ,\"microsoft.containerregistry/registries/webhooks\": { \"SingularDisplayName\": \"Container registry webhook\" }\r\n ,\"microsoft.containerservice/fleets\": { \"SingularDisplayName\": \"Kubernetes fleet manager\" }\r\n ,\"microsoft.containerservice/managedclusters\": { \"SingularDisplayName\": \"Kubernetes service\" }\r\n ,\"microsoft.containerservice/managedclusters/managednamespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes namespace\" }\r\n ,\"microsoft.containerservice/managedclusters/namespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\r\n ,\"microsoft.containerservice/managedclustersnapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService managedclustersnapshot\" }\r\n ,\"microsoft.containerservice/snapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService snapshot\" }\r\n ,\"microsoft.containerstorage/pools\": { \"SingularDisplayName\": \"Container storage\" }\r\n ,\"microsoft.costmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.CostManagement alert\" }\r\n ,\"microsoft.costmanagement/budgets\": { \"SingularDisplayName\": \"Microsoft.CostManagement budget\" }\r\n ,\"microsoft.costmanagement/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement cloud connector\" }\r\n ,\"microsoft.costmanagement/connectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement connector\" }\r\n ,\"microsoft.costmanagement/costallocationrules\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost allocation rule\" }\r\n ,\"microsoft.costmanagement/costdetailsoperationresults\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost details operation result\" }\r\n ,\"microsoft.costmanagement/exports\": { \"SingularDisplayName\": \"Microsoft.CostManagement export\" }\r\n ,\"microsoft.costmanagement/externalbillingaccounts\": { \"SingularDisplayName\": \"Microsoft.CostManagement external billing account\" }\r\n ,\"microsoft.costmanagement/externalsubscriptions\": { \"SingularDisplayName\": \"Microsoft.CostManagement external subscription\" }\r\n ,\"microsoft.costmanagement/markuprules\": { \"SingularDisplayName\": \"Microsoft.CostManagement markup rule\" }\r\n ,\"microsoft.costmanagement/operationstatus\": { \"SingularDisplayName\": \"Microsoft.CostManagement operation statu\" }\r\n ,\"microsoft.costmanagement/reportconfigs\": { \"SingularDisplayName\": \"Microsoft.CostManagement reportconfig\" }\r\n ,\"microsoft.costmanagement/reports\": { \"SingularDisplayName\": \"Microsoft.CostManagement report\" }\r\n ,\"microsoft.costmanagement/scheduledactions\": { \"SingularDisplayName\": \"Microsoft.CostManagement scheduled action\" }\r\n ,\"microsoft.costmanagement/settings\": { \"SingularDisplayName\": \"Microsoft.CostManagement setting\" }\r\n ,\"microsoft.costmanagement/views\": { \"SingularDisplayName\": \"Microsoft.CostManagement view\" }\r\n ,\"microsoft.customerlockbox/requests\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox request\" }\r\n ,\"microsoft.customerlockbox/tenantoptedin\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox tenant opted in\" }\r\n ,\"microsoft.customproviders/associations\": { \"SingularDisplayName\": \"Microsoft.CustomProviders association\" }\r\n ,\"microsoft.customproviders/resourceproviders\": { \"SingularDisplayName\": \"Microsoft.CustomProviders resource provider\" }\r\n ,\"microsoft.dashboard/dashboards\": { \"SingularDisplayName\": \"Azure Monitor dashboards with Grafana\" }\r\n ,\"microsoft.dashboard/grafana\": { \"SingularDisplayName\": \"Azure Managed Grafana\" }\r\n ,\"microsoft.dataaccelerator/indexclusters\": { \"SingularDisplayName\": \"Microsoft.DataAccelerator index cluster\" }\r\n ,\"microsoft.databasefleetmanager/fleets\": { \"SingularDisplayName\": \"Database fleet manager\" }\r\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces\": { \"SingularDisplayName\": \"Fleetspaces\" }\r\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces/databases\": { \"SingularDisplayName\": \"Fleet managed database\" }\r\n ,\"microsoft.databasefleetmanager/fleets/tiers\": { \"SingularDisplayName\": \"tier\" }\r\n ,\"microsoft.databasewatcher/watchers\": { \"SingularDisplayName\": \"Database watcher\" }\r\n ,\"microsoft.databox/jobs\": { \"SingularDisplayName\": \"Azure Data Box\" }\r\n ,\"microsoft.databoxedge/databoxedgedevices\": { \"SingularDisplayName\": \"Azure Stack Edge / Data Box Gateway\" }\r\n ,\"microsoft.databricks/accessconnectors\": { \"SingularDisplayName\": \"Access Connector for Azure Databricks\" }\r\n ,\"microsoft.databricks/workspaces\": { \"SingularDisplayName\": \"Azure Databricks Service\" }\r\n ,\"microsoft.datacatalog/catalogs\": { \"SingularDisplayName\": \"Data catalog\" }\r\n ,\"microsoft.datacollaboration/workspaces\": { \"SingularDisplayName\": \"Project CI\" }\r\n ,\"microsoft.datadog/agreements\": { \"SingularDisplayName\": \"Microsoft.Datadog agreement\" }\r\n ,\"microsoft.datadog/monitors\": { \"SingularDisplayName\": \"Datadog\" }\r\n ,\"microsoft.datadog/subscriptionstatuses\": { \"SingularDisplayName\": \"Microsoft.Datadog subscription statuse\" }\r\n ,\"microsoft.datafactory/datafactories\": { \"SingularDisplayName\": \"Data factory\" }\r\n ,\"microsoft.datafactory/factories\": { \"SingularDisplayName\": \"Data factory (V2)\" }\r\n ,\"microsoft.datafactory/factories/pipelines\": { \"SingularDisplayName\": \"Data Factory pipeline\" }\r\n ,\"microsoft.datafactory/factories/triggers\": { \"SingularDisplayName\": \"Data Factory trigger\" }\r\n ,\"microsoft.datalakeanalytics/accounts\": { \"SingularDisplayName\": \"Data Lake Analytics account\" }\r\n ,\"microsoft.datalakestore/accounts\": { \"SingularDisplayName\": \"Data Lake Storage Gen1\" }\r\n ,\"microsoft.datamigration/databasemigrations\": { \"SingularDisplayName\": \"Microsoft.DataMigration database migration\" }\r\n ,\"microsoft.datamigration/migrationservices\": { \"SingularDisplayName\": \"Microsoft.DataMigration migration service\" }\r\n ,\"microsoft.datamigration/services\": { \"SingularDisplayName\": \"Azure Database Migration Service (classic)\" }\r\n ,\"microsoft.datamigration/services/projects\": { \"SingularDisplayName\": \"Azure Database Migration Project\" }\r\n ,\"microsoft.datamigration/sqlmigrationservices\": { \"SingularDisplayName\": \"Azure Database Migration Service\" }\r\n ,\"microsoft.dataprotection/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\r\n ,\"microsoft.dataprotection/resourceguards\": { \"SingularDisplayName\": \"Resource Guard\" }\r\n ,\"microsoft.datareplication/replicationfabrics\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabric\" }\r\n ,\"microsoft.datareplication/replicationfabrics/fabricagents\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agent\" }\r\n ,\"microsoft.datareplication/replicationfabrics/fabricagents/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agents operation\" }\r\n ,\"microsoft.datareplication/replicationfabrics/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics operation\" }\r\n ,\"microsoft.datareplication/replicationvaults\": { \"SingularDisplayName\": \"Data replication vault\" }\r\n ,\"microsoft.datareplication/replicationvaults/alertsettings\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults alert setting\" }\r\n ,\"microsoft.datareplication/replicationvaults/events\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults event\" }\r\n ,\"microsoft.datareplication/replicationvaults/jobs\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults job\" }\r\n ,\"microsoft.datareplication/replicationvaults/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults jobs operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnectionproxies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection proxy\" }\r\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection\" }\r\n ,\"microsoft.datareplication/replicationvaults/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private link resource\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected item\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems/recoverypoints\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items recovery point\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationextensions\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extension\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationextensions/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extensions operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policy\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policies operation\" }\r\n ,\"microsoft.datashare/accounts\": { \"SingularDisplayName\": \"Data Share\" }\r\n ,\"microsoft.dbformariadb/servers\": { \"SingularDisplayName\": \"Azure Database for MariaDB server\" }\r\n ,\"microsoft.dbformysql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for MySQL flexible server\" }\r\n ,\"microsoft.dbformysql/servers\": { \"SingularDisplayName\": \"MySQL server\" }\r\n ,\"microsoft.dbforpostgresql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for PostgreSQL flexible server\" }\r\n ,\"microsoft.dbforpostgresql/servergroupsv2\": { \"SingularDisplayName\": \"Azure Cosmos DB for PostgreSQL Cluster\" }\r\n ,\"microsoft.dbforpostgresql/servers\": { \"SingularDisplayName\": \"PostgreSQL server\" }\r\n ,\"microsoft.delegatednetwork/controller\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork controller\" }\r\n ,\"microsoft.delegatednetwork/delegatedsubnets\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork delegated subnet\" }\r\n ,\"microsoft.delegatednetwork/orchestrators\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork orchestrator\" }\r\n ,\"microsoft.dependencymap/maps\": { \"SingularDisplayName\": \"Microsoft.DependencyMap map\" }\r\n ,\"microsoft.dependencymap/maps/discoverysources\": { \"SingularDisplayName\": \"Microsoft.DependencyMap maps discovery source\" }\r\n ,\"microsoft.deploymentmanager/artifactsources\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager artifact source\" }\r\n ,\"microsoft.deploymentmanager/rollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topology\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies/services\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies service\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies/services/serviceunits\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies services service unit\" }\r\n ,\"microsoft.deploymentmanager/steps\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager step\" }\r\n ,\"microsoft.desktopvirtualization/appattachpackages\": { \"SingularDisplayName\": \"App attach package\" }\r\n ,\"microsoft.desktopvirtualization/applicationgroups\": { \"SingularDisplayName\": \"Application group\" }\r\n ,\"microsoft.desktopvirtualization/hostpools\": { \"SingularDisplayName\": \"Host pool\" }\r\n ,\"microsoft.desktopvirtualization/scalingplans\": { \"SingularDisplayName\": \"Scaling plan\" }\r\n ,\"microsoft.desktopvirtualization/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.devai/instances\": { \"SingularDisplayName\": \"Microsoft.DevAI instance\" }\r\n ,\"microsoft.devai/instances/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances experiment\" }\r\n ,\"microsoft.devai/instances/sandboxes\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandbox\" }\r\n ,\"microsoft.devai/instances/sandboxes/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandboxes experiment\" }\r\n ,\"microsoft.devcenter/devcenters\": { \"SingularDisplayName\": \"Dev center\" }\r\n ,\"microsoft.devcenter/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Dev Box definition\" }\r\n ,\"microsoft.devcenter/networkconnections\": { \"SingularDisplayName\": \"Network connection\" }\r\n ,\"microsoft.devcenter/plans\": { \"SingularDisplayName\": \"Dev center plan\" }\r\n ,\"microsoft.devcenter/projects\": { \"SingularDisplayName\": \"Project\" }\r\n ,\"microsoft.devcenter/projects/pools\": { \"SingularDisplayName\": \"Pool\" }\r\n ,\"microsoft.developmentwindows365/developmentcloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.DevelopmentWindows365 development cloud pc delegated msi\" }\r\n ,\"microsoft.devhub/iacprofiles\": { \"SingularDisplayName\": \"Infrastructure as Code Automation\" }\r\n ,\"microsoft.devhub/templates\": { \"SingularDisplayName\": \"Microsoft.DevHub template\" }\r\n ,\"microsoft.devhub/templates/versions\": { \"SingularDisplayName\": \"Microsoft.DevHub templates version\" }\r\n ,\"microsoft.devhub/workflows\": { \"SingularDisplayName\": \"Microsoft.DevHub workflow\" }\r\n ,\"microsoft.deviceonboarding/discoveryservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery service\" }\r\n ,\"microsoft.deviceonboarding/discoveryservices/ownershipvoucherpublickeys\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery services ownership voucher public key\" }\r\n ,\"microsoft.deviceonboarding/onboardingservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding service\" }\r\n ,\"microsoft.deviceonboarding/onboardingservices/policies\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding services policy\" }\r\n ,\"microsoft.deviceregistry/assetendpointprofiles\": { \"SingularDisplayName\": \"IoT Asset Endpoint Profile\" }\r\n ,\"microsoft.deviceregistry/assets\": { \"SingularDisplayName\": \"IoT Asset\" }\r\n ,\"microsoft.deviceregistry/billingcontainers\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry billing container\" }\r\n ,\"microsoft.deviceregistry/devices\": { \"SingularDisplayName\": \"IoT Device\" }\r\n ,\"microsoft.deviceregistry/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset\" }\r\n ,\"microsoft.deviceregistry/namespaces\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespace\" }\r\n ,\"microsoft.deviceregistry/namespaces/assetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/namespaces/assets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset\" }\r\n ,\"microsoft.deviceregistry/namespaces/devices\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces device\" }\r\n ,\"microsoft.deviceregistry/namespaces/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/namespaces/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset\" }\r\n ,\"microsoft.deviceregistry/schemaregistries\": { \"SingularDisplayName\": \"IoT Schema Registry\" }\r\n ,\"microsoft.deviceregistry/schemaregistries/schemas\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schema\" }\r\n ,\"microsoft.deviceregistry/schemaregistries/schemas/schemaversions\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schemas schema version\" }\r\n ,\"microsoft.devices/iothubs\": { \"SingularDisplayName\": \"IoT hub\" }\r\n ,\"microsoft.devices/provisioningservices\": { \"SingularDisplayName\": \"Azure IoT Hub Device Provisioning Service (DPS)\" }\r\n ,\"microsoft.deviceupdate/accounts\": { \"SingularDisplayName\": \"Device Update for IoT Hub\" }\r\n ,\"microsoft.deviceupdate/updateaccounts\": { \"SingularDisplayName\": \"Device Update Account\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/activedeployments\": { \"SingularDisplayName\": \"Device Update Active Deployment\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/agents\": { \"SingularDisplayName\": \"Device Update Agent\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/deployments\": { \"SingularDisplayName\": \"Device Update Deployment\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/deviceclasses\": { \"SingularDisplayName\": \"Device Update Device Class\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/updates\": { \"SingularDisplayName\": \"Device Update\" }\r\n ,\"microsoft.devops/pipelines\": { \"SingularDisplayName\": \"Microsoft.DevOps pipeline\" }\r\n ,\"microsoft.devopsinfrastructure/pools\": { \"SingularDisplayName\": \"Managed DevOps Pool\" }\r\n ,\"microsoft.devspaces/controllers\": { \"SingularDisplayName\": \"Microsoft.DevSpaces controller\" }\r\n ,\"microsoft.devtestlab/labs\": { \"SingularDisplayName\": \"DevTest lab\" }\r\n ,\"microsoft.devtestlab/labs/virtualmachines\": { \"SingularDisplayName\": \"DevTest Lab virtual machine\" }\r\n ,\"microsoft.devtestlab/schedules\": { \"SingularDisplayName\": \"Microsoft.DevTestLab schedule\" }\r\n ,\"microsoft.devtunnels/tunnelplans\": { \"SingularDisplayName\": \"Dev Tunnels Domain\" }\r\n ,\"microsoft.diagnostics/apollo\": { \"SingularDisplayName\": \"Microsoft.Diagnostics apollo\" }\r\n ,\"microsoft.digitaltwins/digitaltwinsinstances\": { \"SingularDisplayName\": \"Azure Digital Twins\" }\r\n ,\"microsoft.discovery/agents\": { \"SingularDisplayName\": \"Microsoft Discovery Agent\" }\r\n ,\"microsoft.discovery/bookshelves\": { \"SingularDisplayName\": \"Microsoft Discovery Bookshelf\" }\r\n ,\"microsoft.discovery/datacontainers\": { \"SingularDisplayName\": \"Microsoft Discovery Data Container\" }\r\n ,\"microsoft.discovery/datacontainers/dataassets\": { \"SingularDisplayName\": \"Data asset\" }\r\n ,\"microsoft.discovery/models\": { \"SingularDisplayName\": \"Microsoft Discovery Model\" }\r\n ,\"microsoft.discovery/storages\": { \"SingularDisplayName\": \"Microsoft Discovery Storage\" }\r\n ,\"microsoft.discovery/supercomputers\": { \"SingularDisplayName\": \"Microsoft Discovery Supercomputer\" }\r\n ,\"microsoft.discovery/supercomputers/nodepools\": { \"SingularDisplayName\": \"Nodepool\" }\r\n ,\"microsoft.discovery/tools\": { \"SingularDisplayName\": \"Microsoft Discovery Tool\" }\r\n ,\"microsoft.discovery/workflows\": { \"SingularDisplayName\": \"Microsoft Discovery Workflow\" }\r\n ,\"microsoft.discovery/workspaces\": { \"SingularDisplayName\": \"Microsoft Discovery Workspace\" }\r\n ,\"microsoft.discovery/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft Discovery Project\" }\r\n ,\"microsoft.documentdb/cassandraclusters\": { \"SingularDisplayName\": \"Azure Managed Instance for Apache Cassandra\" }\r\n ,\"microsoft.documentdb/databaseaccounts\": { \"SingularDisplayName\": \"Cosmos DB account\" }\r\n ,\"microsoft.documentdb/fleets\": { \"SingularDisplayName\": \"Azure Cosmos DB Fleet\" }\r\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccounts\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\r\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccountswithlocations\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\r\n ,\"microsoft.documentdb/mongoclusters\": { \"SingularDisplayName\": \"Azure Cosmos DB for MongoDB (vCore)\" }\r\n ,\"microsoft.documentdb/throughputpools\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pool\" }\r\n ,\"microsoft.documentdb/throughputpools/throughputpoolaccounts\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pools throughput pool account\" }\r\n ,\"microsoft.domainregistration/domains\": { \"SingularDisplayName\": \"App Service Domain\" }\r\n ,\"microsoft.domainregistration/topleveldomains\": { \"SingularDisplayName\": \"Microsoft.DomainRegistration top level domain\" }\r\n ,\"microsoft.durabletask/namespaces\": { \"SingularDisplayName\": \"Microsoft.DurableTask namespace\" }\r\n ,\"microsoft.durabletask/namespaces/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\r\n ,\"microsoft.durabletask/schedulers\": { \"SingularDisplayName\": \"Durable Task Scheduler\" }\r\n ,\"microsoft.durabletask/schedulers/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\r\n ,\"microsoft.dynamics365fraudprotection/instances\": { \"SingularDisplayName\": \"Microsoft.Dynamics365FraudProtection instance\" }\r\n ,\"microsoft.easm/workspaces\": { \"SingularDisplayName\": \"Microsoft Defender EASM\" }\r\n ,\"microsoft.edge/configurations\": { \"SingularDisplayName\": \"Site configuration\" }\r\n ,\"microsoft.edge/configurations/arcgatewayconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations arc gateway configuration\" }\r\n ,\"microsoft.edge/configurations/connectivityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations connectivity configuration\" }\r\n ,\"microsoft.edge/configurations/dynamicconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configuration\" }\r\n ,\"microsoft.edge/configurations/dynamicconfigurations/versions\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configurations version\" }\r\n ,\"microsoft.edge/configurations/networkconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations network configuration\" }\r\n ,\"microsoft.edge/configurations/securityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations security configuration\" }\r\n ,\"microsoft.edge/configurations/timeserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations time server configuration\" }\r\n ,\"microsoft.edge/connectivitystatuses\": { \"SingularDisplayName\": \"Microsoft.Edge connectivity statuse\" }\r\n ,\"microsoft.edge/disconnectedoperations\": { \"SingularDisplayName\": \"Azure Local - disconnected operations\" }\r\n ,\"microsoft.edge/siteawareresourcetypes\": { \"SingularDisplayName\": \"Microsoft.Edge site aware resource type\" }\r\n ,\"microsoft.edge/sites\": { \"SingularDisplayName\": \"Site manager - Azure Arc\" }\r\n ,\"microsoft.edge/updates\": { \"SingularDisplayName\": \"Microsoft.Edge update\" }\r\n ,\"microsoft.edgemarketplace/offers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace offer\" }\r\n ,\"microsoft.edgemarketplace/publishers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace publisher\" }\r\n ,\"microsoft.edgeorder/addresses\": { \"SingularDisplayName\": \"Azure Edge Hardware Center Address\" }\r\n ,\"microsoft.edgeorder/bootstrapconfigurations\": { \"SingularDisplayName\": \"Site Key\" }\r\n ,\"microsoft.edgeorder/orderitems\": { \"SingularDisplayName\": \"Azure Edge Hardware Center\" }\r\n ,\"microsoft.edgeorder/virtual_orderitems\": { \"SingularDisplayName\": \"Device\" }\r\n ,\"microsoft.edgezones/extendedzones\": { \"SingularDisplayName\": \"Microsoft.EdgeZones extended zone\" }\r\n ,\"microsoft.education/grants\": { \"SingularDisplayName\": \"Microsoft.Education grant\" }\r\n ,\"microsoft.education/labs\": { \"SingularDisplayName\": \"Microsoft.Education lab\" }\r\n ,\"microsoft.education/labs/joinrequests\": { \"SingularDisplayName\": \"Microsoft.Education labs join request\" }\r\n ,\"microsoft.education/labs/students\": { \"SingularDisplayName\": \"Microsoft.Education labs student\" }\r\n ,\"microsoft.education/studentlabs\": { \"SingularDisplayName\": \"Microsoft.Education student lab\" }\r\n ,\"microsoft.elastic/monitors\": { \"SingularDisplayName\": \"Elastic Cloud Resource\" }\r\n ,\"microsoft.elasticsan/elasticsans\": { \"SingularDisplayName\": \"Elastic SAN\" }\r\n ,\"microsoft.energydataplatform/energyservices\": { \"SingularDisplayName\": \"Microsoft.EnergyDataPlatform energy service\" }\r\n ,\"microsoft.enterpriseknowledgegraph/services\": { \"SingularDisplayName\": \"Microsoft.EnterpriseKnowledgeGraph service\" }\r\n ,\"microsoft.enterprisesupport/enterprisesupports\": { \"SingularDisplayName\": \"Microsoft.EnterpriseSupport enterprise support\" }\r\n ,\"microsoft.eventgrid/domains\": { \"SingularDisplayName\": \"Event Grid Domain\" }\r\n ,\"microsoft.eventgrid/domains/topics\": { \"SingularDisplayName\": \"Event Grid Domain Topic\" }\r\n ,\"microsoft.eventgrid/eventsubscriptions\": { \"SingularDisplayName\": \"Microsoft.EventGrid event subscription\" }\r\n ,\"microsoft.eventgrid/extensiontopics\": { \"SingularDisplayName\": \"Event Grid extension topic\" }\r\n ,\"microsoft.eventgrid/namespaces\": { \"SingularDisplayName\": \"Event Grid Namespace\" }\r\n ,\"microsoft.eventgrid/namespaces/topics\": { \"SingularDisplayName\": \"Event Grid Namespace Topic\" }\r\n ,\"microsoft.eventgrid/namespaces/topics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Subscription\" }\r\n ,\"microsoft.eventgrid/namespaces/topicspaces\": { \"SingularDisplayName\": \"Event Grid Topic Space\" }\r\n ,\"microsoft.eventgrid/partnerconfigurations\": { \"SingularDisplayName\": \"Event Grid Partner Configuration\" }\r\n ,\"microsoft.eventgrid/partnerdestinations\": { \"SingularDisplayName\": \"Event Grid Partner Destination\" }\r\n ,\"microsoft.eventgrid/partnernamespaces\": { \"SingularDisplayName\": \"Event Grid Partner Namespace\" }\r\n ,\"microsoft.eventgrid/partnernamespaces/channels\": { \"SingularDisplayName\": \"Event Grid Channel\" }\r\n ,\"microsoft.eventgrid/partnerregistrations\": { \"SingularDisplayName\": \"Event Grid Partner Registration\" }\r\n ,\"microsoft.eventgrid/partnertopics\": { \"SingularDisplayName\": \"Event Grid Partner Topic\" }\r\n ,\"microsoft.eventgrid/systemtopics\": { \"SingularDisplayName\": \"Event Grid System Topic\" }\r\n ,\"microsoft.eventgrid/systemtopics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Grid Subscriptions\" }\r\n ,\"microsoft.eventgrid/topics\": { \"SingularDisplayName\": \"Event Grid Topic\" }\r\n ,\"microsoft.eventgrid/topictypes\": { \"SingularDisplayName\": \"Microsoft.EventGrid topic type\" }\r\n ,\"microsoft.eventgrid/verifiedpartners\": { \"SingularDisplayName\": \"Microsoft.EventGrid verified partner\" }\r\n ,\"microsoft.eventhub/clusters\": { \"SingularDisplayName\": \"Event Hubs Cluster\" }\r\n ,\"microsoft.eventhub/namespaces\": { \"SingularDisplayName\": \"Event Hubs namespace\" }\r\n ,\"microsoft.eventhub/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Event Hubs Geo-DR Alias\" }\r\n ,\"microsoft.eventhub/namespaces/eventhubs\": { \"SingularDisplayName\": \"Event Hubs Instance\" }\r\n ,\"microsoft.eventhub/namespaces/providers/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\r\n ,\"microsoft.eventhub/namespaces/schemagroups\": { \"SingularDisplayName\": \"Schema Group\" }\r\n ,\"microsoft.experimentation/experimentworkspaces\": { \"SingularDisplayName\": \"Experiment Workspace\" }\r\n ,\"microsoft.extendedlocation/customlocations\": { \"SingularDisplayName\": \"Custom location\" }\r\n ,\"microsoft.fabric/capacities\": { \"SingularDisplayName\": \"Fabric Capacity\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/operationresults\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric operation result\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private endpoint connection\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private link resource\" }\r\n ,\"microsoft.fairfieldgardens/deviceprovisioningstates\": { \"SingularDisplayName\": \"Microsoft.FairfieldGardens device provisioning state\" }\r\n ,\"microsoft.fairfieldgardens/provisioningresources\": { \"SingularDisplayName\": \"Fairfield Gardens\" }\r\n ,\"microsoft.fairfieldgardens/provisioningresources/provisioningpolicies\": { \"SingularDisplayName\": \"Provisioning policy\" }\r\n ,\"microsoft.falcon/namespaces\": { \"SingularDisplayName\": \"Microsoft.Falcon namespace\" }\r\n ,\"microsoft.features/featureprovidernamespaces/featureconfigurations\": { \"SingularDisplayName\": \"Preview features\" }\r\n ,\"microsoft.fidalgo/devcenters\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenter\" }\r\n ,\"microsoft.fidalgo/devcenters/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters attachednetwork\" }\r\n ,\"microsoft.fidalgo/devcenters/catalogs\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalog\" }\r\n ,\"microsoft.fidalgo/devcenters/catalogs/items\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalogs item\" }\r\n ,\"microsoft.fidalgo/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters devboxdefinition\" }\r\n ,\"microsoft.fidalgo/devcenters/environmenttypes\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters environment type\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters gallery\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries/images\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries image\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries/images/versions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries images version\" }\r\n ,\"microsoft.fidalgo/devcenters/mappings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters mapping\" }\r\n ,\"microsoft.fidalgo/machinedefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo machinedefinition\" }\r\n ,\"microsoft.fidalgo/networksettings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksetting\" }\r\n ,\"microsoft.fidalgo/networksettings/healthchecks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksettings healthcheck\" }\r\n ,\"microsoft.fidalgo/projects\": { \"SingularDisplayName\": \"Microsoft.Fidalgo project\" }\r\n ,\"microsoft.fidalgo/projects/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects attachednetwork\" }\r\n ,\"microsoft.fidalgo/projects/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects devboxdefinition\" }\r\n ,\"microsoft.fidalgo/projects/environments\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects environment\" }\r\n ,\"microsoft.fidalgo/projects/pools\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects pool\" }\r\n ,\"microsoft.fileshares/fileshares\": { \"SingularDisplayName\": \"File share\" }\r\n ,\"microsoft.fluidrelay/fluidrelayservers\": { \"SingularDisplayName\": \"Fluid Relay\" }\r\n ,\"microsoft.footprintmonitoring/profiles\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profile\" }\r\n ,\"microsoft.footprintmonitoring/profiles/experiments\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles experiment\" }\r\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoint\" }\r\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints/conditions\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoints condition\" }\r\n ,\"microsoft.gallery/myareas/galleryitems\": { \"SingularDisplayName\": \"Template\" }\r\n ,\"microsoft.genomics/accounts\": { \"SingularDisplayName\": \"Genomics account\" }\r\n ,\"microsoft.graph/azureadapplication\": { \"SingularDisplayName\": \"Entra application\" }\r\n ,\"microsoft.graph/azureadapplicationprototype\": { \"SingularDisplayName\": \"Microsoft.Graph Azure ad application prototype\" }\r\n ,\"microsoft.graphservices/accounts\": { \"SingularDisplayName\": \"Metered API account\" }\r\n ,\"microsoft.guestconfiguration/guestconfigurationassignments\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignment\" }\r\n ,\"microsoft.guestconfiguration/guestconfigurationassignments/reports\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignments report\" }\r\n ,\"microsoft.hanaonazure/hanainstances\": { \"SingularDisplayName\": \"SAP HANA on Azure\" }\r\n ,\"microsoft.hanaonazure/sapmonitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP Solutions (classic)\" }\r\n ,\"microsoft.hardware/orders\": { \"SingularDisplayName\": \"Microsoft.Hardware order\" }\r\n ,\"microsoft.hardwaresecuritymodules/cloudhsmclusters\": { \"SingularDisplayName\": \"Azure Cloud HSM\" }\r\n ,\"microsoft.hdinsight/clusterpools\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster pool\" }\r\n ,\"microsoft.hdinsight/clusterpools/clusters\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster\" }\r\n ,\"microsoft.hdinsight/clusterpools/clusters/instanceviews\": { \"SingularDisplayName\": \"Microsoft.HDInsight clusterpools clusters instance view\" }\r\n ,\"microsoft.hdinsight/clusters\": { \"SingularDisplayName\": \"HDInsight cluster\" }\r\n ,\"microsoft.healthbot/healthbots\": { \"SingularDisplayName\": \"Healthcare agent service\" }\r\n ,\"microsoft.healthcareapis/services\": { \"SingularDisplayName\": \"Azure API for FHIR\" }\r\n ,\"microsoft.healthcareapis/workspaces\": { \"SingularDisplayName\": \"Health Data Services workspace\" }\r\n ,\"microsoft.healthcareapis/workspaces/dicomservices\": { \"SingularDisplayName\": \"DICOM service\" }\r\n ,\"microsoft.healthcareapis/workspaces/fhirservices\": { \"SingularDisplayName\": \"FHIR service\" }\r\n ,\"microsoft.healthcareapis/workspaces/iotconnectors\": { \"SingularDisplayName\": \"MedTech service\" }\r\n ,\"microsoft.healthdataaiservices/deidservices\": { \"SingularDisplayName\": \"De-identification Service\" }\r\n ,\"microsoft.healthmodel/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\r\n ,\"microsoft.healthplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.HealthPlatform account\" }\r\n ,\"microsoft.help/diagnostics\": { \"SingularDisplayName\": \"Microsoft.Help diagnostic\" }\r\n ,\"microsoft.help/selfhelp\": { \"SingularDisplayName\": \"Microsoft.Help self help\" }\r\n ,\"microsoft.help/simplifiedsolutions\": { \"SingularDisplayName\": \"Microsoft.Help simplified solution\" }\r\n ,\"microsoft.help/solutions\": { \"SingularDisplayName\": \"Microsoft.Help solution\" }\r\n ,\"microsoft.help/troubleshooters\": { \"SingularDisplayName\": \"Microsoft.Help troubleshooter\" }\r\n ,\"microsoft.hpcworkbench/instances\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instance\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chamber\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/accessprofiles\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers access profile\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/filerequests\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file request\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/files\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/storages\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers storage\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/workloads\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers workload\" }\r\n ,\"microsoft.hpcworkbench/instances/consortiums\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances consortium\" }\r\n ,\"microsoft.hybridcloud/cloudconnections\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connection\" }\r\n ,\"microsoft.hybridcloud/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connector\" }\r\n ,\"microsoft.hybridcompute/arcgatewayassociatedresources\": { \"SingularDisplayName\": \"Arc gateway associated resource\" }\r\n ,\"microsoft.hybridcompute/arcserverwithwac\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/gateways\": { \"SingularDisplayName\": \"Arc gateway\" }\r\n ,\"microsoft.hybridcompute/licenses\": { \"SingularDisplayName\": \"Extended Security Updates - Windows Server 2012/R2\" }\r\n ,\"microsoft.hybridcompute/machines\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machines/microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\r\n ,\"microsoft.hybridcompute/machines/microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\r\n ,\"microsoft.hybridcompute/machines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.hybridcompute/machinesesu\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinespaygo\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinessoftwareassurance\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinessovereign\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Arc Private Link Scope\" }\r\n ,\"microsoft.hybridcompute/settings\": { \"SingularDisplayName\": \"Microsoft.HybridCompute setting\" }\r\n ,\"microsoft.hybridconnectivity/endpoints\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoint\" }\r\n ,\"microsoft.hybridconnectivity/endpoints/serviceconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoints service configuration\" }\r\n ,\"microsoft.hybridconnectivity/publiccloudconnectors\": { \"SingularDisplayName\": \"Multicloud connector\" }\r\n ,\"microsoft.hybridconnectivity/solutionconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configuration\" }\r\n ,\"microsoft.hybridconnectivity/solutionconfigurations/inventory\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configurations inventory\" }\r\n ,\"microsoft.hybridconnectivity/solutiontypes\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution type\" }\r\n ,\"microsoft.hybridcontainerservice/kubernetesversions\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService kubernetes version\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instance\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/agentpools\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances agent pool\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances hybrid identity metadata\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances upgrade profile\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusters\": { \"SingularDisplayName\": \"Kubernetes hybrid - Azure Arc\" }\r\n ,\"microsoft.hybridcontainerservice/skus\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService SKU\" }\r\n ,\"microsoft.hybridcontainerservice/storagespaces\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService storage space\" }\r\n ,\"microsoft.hybridcontainerservice/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService virtual network\" }\r\n ,\"microsoft.hybriddata/datamanagers\": { \"SingularDisplayName\": \"Microsoft.HybridData data manager\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data service\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definition\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions/jobs\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definitions job\" }\r\n ,\"microsoft.hybriddata/datamanagers/datastores\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store\" }\r\n ,\"microsoft.hybriddata/datamanagers/datastoretypes\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store type\" }\r\n ,\"microsoft.hybriddata/datamanagers/publickeys\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers public key\" }\r\n ,\"microsoft.hybridnetwork/configurationgroupvalues\": { \"SingularDisplayName\": \"Configuration Group Value\" }\r\n ,\"microsoft.hybridnetwork/devices\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Device\" }\r\n ,\"microsoft.hybridnetwork/networkfunctions\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Network Function\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publisher\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/artifactstores\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers artifact store\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers configuration group schema\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition group\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition groups network function definition version\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design group\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design groups network service design version\" }\r\n ,\"microsoft.hybridnetwork/publishers\": { \"SingularDisplayName\": \"Publisher\" }\r\n ,\"microsoft.hybridnetwork/publishers/artifactstores\": { \"SingularDisplayName\": \"Publisher Artifact Store\" }\r\n ,\"microsoft.hybridnetwork/publishers/artifactstores/artifactmanifests\": { \"SingularDisplayName\": \"Publisher Artifact Manifest\" }\r\n ,\"microsoft.hybridnetwork/publishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Configuration Group Schema\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Network Function Definition\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Network Function Definition Version\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Network Service Design\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Network Service Design Version\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management container\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rolloutsequences\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout sequence\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rollouttiers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout tier\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specification\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollout\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts/statuses\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollouts statuse\" }\r\n ,\"microsoft.hybridnetwork/sitenetworkservices\": { \"SingularDisplayName\": \"Site Network Service\" }\r\n ,\"microsoft.hybridnetwork/sites\": { \"SingularDisplayName\": \"Site\" }\r\n })[tolower(id)]\r\n}\r\n", + "$fxv#10": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Prices |=========================================================================================================\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='Transforms Prices_raw into FOCUS 1.2.', folder='Prices')\r\nPrices_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n let prices = materialize(\r\n Prices_raw\r\n | extend PricingCurrency = coalesce(Currency, CurrencyCode) // CurrencyCode last as a fallback only\r\n | extend x_SkuId = coalesce(SkuId, SkuID)\r\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\r\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\r\n | extend x_SkuTerm = isoMonths(Term)\r\n | project-rename\r\n SkuMeter = MeterName,\r\n x_BaseUnitPrice = BasePrice,\r\n x_EffectivePeriodEnd = EffectiveEndDate,\r\n x_EffectivePeriodStart = EffectiveStartDate,\r\n x_PricingUnitDescription = UnitOfMeasure,\r\n x_SkuIncludedQuantity = IncludedQuantity,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuMeterType = MeterType,\r\n x_SkuOfferId = OfferID,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPriceType = PriceType,\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTier = TierMinimumUnits\r\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, real(null)) // UnitPrice for savings plan is not the on-demand unit price\r\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, real(null)) // MarketPrice for savings plan is not the list price\r\n | extend ChargeCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Usage',\r\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\r\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\r\n ''\r\n )\r\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\r\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\r\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\r\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\r\n //\r\n // Get latest ingested row based on the unique ID\r\n | extend x_IngestionTime = ingestion_time()\r\n );\r\n //\r\n // Meters for reservations and savings plans to identify commitment eligibility\r\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\r\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\r\n //\r\n // Copy list/base/contracted prices from on-demand SKUs\r\n prices\r\n | where x_SkuPriceType == 'SavingsPlan'\r\n // If we use join, specify the shuffle key\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\r\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\r\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\r\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\r\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\r\n //\r\n // Set CommitmentDiscountCategory for reuse\r\n | extend CommitmentDiscountCategory = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Usage',\r\n x_SkuPriceType == 'SavingsPlan', 'Spend',\r\n ''\r\n )\r\n //\r\n // Calculate commitment discount eligibility\r\n // TODO: Would a join be faster?\r\n // TODO: Check this to ensure it's correct\r\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\r\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\r\n //\r\n // TODO: Implement x_CommitmentDiscountNormalizedRatio\r\n | extend x_CommitmentDiscountNormalizedRatio = real(null)\r\n //\r\n // Add PricingUnit and x_PricingBlockSize\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\r\n | lookup kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n //\r\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, real(null)) // Savings plan prices are for the effective price, not the contracted price\r\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\r\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\r\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\r\n | project\r\n BillingAccountId = tolower(case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n BillingAccountId startswith '/', BillingAccountId,\r\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\r\n )),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\r\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\r\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\r\n ''\r\n ),\r\n CommitmentDiscountUnit = case(\r\n isempty(CommitmentDiscountCategory), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), PricingUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', PricingUnit),\r\n ''\r\n ),\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed',\r\n ''\r\n ),\r\n PricingCurrency,\r\n PricingUnit,\r\n SkuId = coalesce(ProductId, ProductID),\r\n SkuMeter,\r\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n strlen(x_BillingAccountId) > 32, 'MCA',\r\n strlen(x_BillingAccountId) < 32, 'EA',\r\n 'Unknown'\r\n ),\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\r\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory = case(\r\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_SkuDescription = Product,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\r\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\r\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\r\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\r\n}\r\n\r\n// Prices_final_v1_2 table\r\n.create-merge table Prices_final_v1_2 (\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n ChargeCategory: string,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountType: string,\r\n CommitmentDiscountUnit: string,\r\n ContractedUnitPrice: real,\r\n ListUnitPrice: real,\r\n PricingCategory: string,\r\n PricingCurrency: string, // Azure\r\n PricingUnit: string,\r\n SkuId: string,\r\n SkuMeter: string, // Azure\r\n SkuPriceId: string,\r\n SkuPriceIdv2: string, // Hubs add-on\r\n x_BaseUnitPrice: real, // Azure\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure MCA\r\n x_BillingProfileId: string, // Azure MCA\r\n x_CommitmentDiscountNormalizedRatio: real, // Hubs add-on\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_ContractedUnitPriceDiscount: real, // Hubs add-on\r\n x_ContractedUnitPriceDiscountPercent: real, // Hubs add-on\r\n x_EffectivePeriodEnd: datetime, // Azure\r\n x_EffectivePeriodStart: datetime, // Azure\r\n x_EffectiveUnitPrice: real, // Azure\r\n x_EffectiveUnitPriceDiscount: real, // Hubs add-on\r\n x_EffectiveUnitPriceDiscountPercent: real, // Hubs add-on\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_PricingBlockSize: real, // Hubs add-on\r\n x_PricingSubcategory: string, // Hubs add-on\r\n x_PricingUnitDescription: string, // Azure\r\n x_SkuDescription: string, // Azure\r\n x_SkuId: string, // Azure\r\n x_SkuIncludedQuantity: real, // Azure EA\r\n x_SkuMeterCategory: string, // Azure\r\n x_SkuMeterId: string, // Azure\r\n x_SkuMeterSubcategory: string, // Azure\r\n x_SkuMeterType: string, // Azure\r\n x_SkuPriceType: string, // Azure\r\n x_SkuProductId: string, // Azure\r\n x_SkuRegion: string, // Azure\r\n x_SkuServiceFamily: string, // Azure\r\n x_SkuOfferId: string, // Azure EA\r\n x_SkuPartNumber: string, // Azure EA\r\n x_SkuTerm: int, // Azure\r\n x_SkuTier: real, // Azure MCA\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_TotalUnitPriceDiscount: real, // Hubs add-on\r\n x_TotalUnitPriceDiscountPercent: real // Hubs add-on\r\n)\r\n\r\n// Update policy for Prices_raw -> Prices_final_v1_2\r\n.alter table Prices_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Prices_raw\",\r\n \"Query\": \"Prices_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Cost and usage |=================================================================================================\r\n// Supported versions:\r\n// - MS: 1.2-preview, 1.0, 1.0-preview(v1)\r\n// https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0\r\n// https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024\r\n// https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 \r\n// https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All costs transformed to FOCUS 1.2.', folder='Costs')\r\nCosts_transform_v1_2()\r\n{\r\n let checkString = (column: string, oldValue: string, newValue: string) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n let checkInt = (column: string, oldValue: int, newValue: int) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n let checkReal = (column: string, oldValue: real, newValue: real) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n Costs_raw\r\n //\r\n // Dedupe rows\r\n | extend x_IngestionTime = ingestion_time()\r\n | extend x_ChargeId = ''\r\n // TODO: Consider adding a unique charge ID per row\r\n // hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // // 1. Resource hierarchy (including resource name), highest to lowest\r\n // BillingAccountId,\r\n // x_InvoiceSectionId,\r\n // x_AccountOwnerId,\r\n // SubAccountId,\r\n // x_ResourceGroupName,\r\n // ResourceName,\r\n // // 2. Resource details\r\n // ResourceId,\r\n // RegionId,\r\n // Tags,\r\n // CommitmentDiscountId,\r\n // x_CostCenter,\r\n // // 4. Meter details\r\n // SkuPriceId,\r\n // x_SkuMeterId,\r\n // x_SkuPartNumber,\r\n // x_SkuOfferId,\r\n // x_SkuDetails,\r\n // // 5. Date\r\n // ChargePeriodStart\r\n // ))\r\n //\r\n // Identify data quality issues\r\n // TODO: Remove x_SourceChanges in v1_3 (or later)\r\n | extend x_SourceChanges = trim_end(',', strcat(\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\r\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\r\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\r\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\r\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\r\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\r\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\r\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\r\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\r\n 'XEffectiveUnitPriceRoundingError,', ''),\r\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\r\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\r\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\r\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\r\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\r\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\r\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\r\n ))\r\n //\r\n // Handle provider columns that moved to FOCUS\r\n | extend PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency)\r\n //\r\n // Backup original prices/costs before the merge\r\n | extend old_ContractedCost = ContractedCost\r\n | extend old_ContractedUnitPrice = ContractedUnitPrice\r\n | extend old_ListCost = ListCost\r\n | extend old_ListUnitPrice = ListUnitPrice\r\n | extend old_x_EffectiveUnitPrice = x_EffectiveUnitPrice\r\n //\r\n // Fix columns needed in other changes\r\n | extend old_ProviderName = ProviderName, ProviderName = case(\r\n isnotempty(ProviderName), ProviderName,\r\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\r\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\r\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\r\n ''\r\n )\r\n //\r\n // Identify source\r\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\r\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\r\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\r\n ''\r\n ))\r\n // Append version check error code\r\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\r\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\r\n )\r\n //\r\n // Fix quantities\r\n | extend old_PricingQuantity = PricingQuantity, PricingQuantity = case(\r\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\r\n PricingQuantity\r\n )\r\n | extend old_ConsumedQuantity = ConsumedQuantity, ConsumedQuantity = case(\r\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\r\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\r\n ConsumedQuantity\r\n )\r\n //\r\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\r\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\r\n and (isempty(ListUnitPrice) or isempty(ContractedUnitPrice) or ListUnitPrice == 0 or ContractedUnitPrice == 0)\r\n and x_EffectiveUnitPrice != 0\r\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\r\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\r\n | as allCosts\r\n | where tmp_MissingPrices\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | as costsWithMissingPrices\r\n | join kind=leftouter (\r\n Prices_final_v1_2\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\r\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\r\n ) on tmp_ReservationPriceLookupKey\r\n //\r\n // Select the best price to use for each row\r\n | extend x_EffectiveUnitPrice = case(\r\n // If price is a rounding error away from the billed price, use the billed price\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\r\n // If price is a rounding error away from the contracted price, use the contracted price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ContractedUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\r\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ListUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // Otherwise, assume the contracted price is the same as list price to support aggregations\r\n ContractedUnitPrice\r\n )\r\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\r\n | extend ContractedCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\r\n // ContractedCost is 0 in all other scenarios...\r\n // If 0 and there's a billed cost and prices are the same, use BilledCost\r\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\r\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\r\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume EffectiveCost\r\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ContractedCost\r\n )\r\n | extend ListCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\r\n // ListCost is 0 in all other scenarios...\r\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\r\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume ContractedCost\r\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ListCost\r\n )\r\n // Merge the rest of the unmodified cost records and remove excess columns\r\n | union (allCosts | where not(tmp_MissingPrices))\r\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\r\n //\r\n | extend SkuPriceDetails = parse_json(SkuPriceDetails)\r\n | extend Tags = parse_json(Tags)\r\n | extend x_SkuDetails = parse_json(x_SkuDetails)\r\n //\r\n // Handle FOCUS 1.0-preview\r\n | extend old_ChargeSubcategory = ChargeSubcategory\r\n | extend old_ChargeCategory = ChargeCategory, ChargeCategory = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Credit', 'Credit',\r\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\r\n ChargeCategory\r\n )\r\n | extend old_ChargeClass = ChargeClass, ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass)\r\n //\r\n // Populate CapacityReservationId when not specified\r\n | extend CapacityReservationId = coalesce(CapacityReservationId, tostring(coalesce(x_SkuDetails.VMCapacityReservationId, SkuPriceDetails.VMCapacityReservationId, SkuPriceDetails.x_VMCapacityReservationId)))\r\n | extend old_CapacityReservationStatus = CapacityReservationStatus, CapacityReservationStatus = case(\r\n isempty(CapacityReservationId), '',\r\n isnotempty(CapacityReservationStatus), CapacityReservationStatus,\r\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\r\n 'Used'\r\n )\r\n //\r\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\r\n | extend old_ChargeFrequency = ChargeFrequency, ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency)\r\n //\r\n // Commitment discounts\r\n | extend x_CommitmentDiscountNormalizedRatio = case(\r\n // Calculate from CommitmentDiscountQuantity, if specified\r\n isnotempty(CommitmentDiscountQuantity) and CommitmentDiscountQuantity != 0, CommitmentDiscountQuantity / PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\r\n // Not applicable\r\n isempty(CommitmentDiscountStatus), real(null),\r\n // Parse from SKU details if not specified explicitly\r\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, SkuPriceDetails.RINormalizationRatio, SkuPriceDetails.x_RINormalizationRatio, dynamic(1)))\r\n )\r\n | extend old_CommitmentDiscountQuantity = CommitmentDiscountQuantity, CommitmentDiscountQuantity = case(\r\n // FOCUS 1.2\r\n isnotempty(CommitmentDiscountQuantity), CommitmentDiscountQuantity,\r\n // FOCUS 1.0-preview, 1.0\r\n isempty(CommitmentDiscountStatus), real(null),\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\r\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\r\n real(null)\r\n )\r\n | extend old_CommitmentDiscountUnit = CommitmentDiscountUnit, CommitmentDiscountUnit = case(\r\n // FOCUS 1.2\r\n isnotempty(CommitmentDiscountUnit), CommitmentDiscountUnit,\r\n // FOCUS 1.0\r\n isempty(CommitmentDiscountQuantity), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\r\n ''\r\n )\r\n | extend old_CommitmentDiscountStatus = CommitmentDiscountStatus, CommitmentDiscountStatus = case(\r\n // FOCUS 1.0+\r\n isnotempty(CommitmentDiscountStatus), CommitmentDiscountStatus,\r\n // FOCUS 1.0-preview\r\n ChargeSubcategory == 'Used Commitment', 'Used',\r\n ChargeSubcategory == 'Unused Commitment', 'Unused',\r\n ''\r\n )\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n //\r\n // Pricing\r\n | extend old_x_AmortizationClass = x_AmortizationClass, x_AmortizationClass = case(\r\n // FOCUS 1.2\r\n isnotempty(x_AmortizationClass), x_AmortizationClass,\r\n // FOCUS 1.0-preview+\r\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\r\n ''\r\n )\r\n | extend old_PricingCategory = PricingCategory, PricingCategory = case(\r\n // FOCUS 1.0+\r\n isnotempty(PricingCategory), PricingCategory,\r\n // FOCUS 1.0-preview\r\n PricingCategory == 'On-Demand', 'Standard',\r\n PricingCategory == 'Commitment-Based', 'Committed',\r\n ''\r\n )\r\n //\r\n // Commitment discount utilization\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n //\r\n // BUG: Fix ContractedCost that has bad values\r\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\r\n //\r\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\r\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), real(null))\r\n | extend old_ConsumedUnit = ConsumedUnit, ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\r\n //\r\n // Convert IDs to lowercase for consistency\r\n | extend BillingAccountId = tolower(BillingAccountId)\r\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\r\n //\r\n // BUG: Remove EffectiveCost for commitment discount purchases\r\n | extend old_EffectiveCost = EffectiveCost, EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), EffectiveCost)\r\n | extend old_x_EffectiveCostInUsd = x_EffectiveCostInUsd, x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), x_EffectiveCostInUsd)\r\n //\r\n // Clean up resource columns\r\n | extend old_ResourceId = ResourceId, ResourceId = case(\r\n isnotempty(ResourceId), ResourceId,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\r\n ResourceId\r\n )\r\n | extend old_ResourceName = ResourceName, ResourceName = tolower(case(\r\n isnotempty(ResourceName), ResourceName,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\r\n ResourceName\r\n ))\r\n | extend old_x_ResourceType = x_ResourceType, x_ResourceType = case(\r\n isnotempty(x_ResourceType), x_ResourceType,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\r\n x_ResourceType\r\n )\r\n | extend old_ResourceType = ResourceType, ResourceType = case(\r\n // Use existing resource type display name unless it's an internal resource type ID\r\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\r\n // Use CommitmentDiscountType for commitment discount purchases\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\r\n // Look up display name from internal type\r\n isnotempty(x_ResourceType), coalesce(tostring(resource_type(x_ResourceType).SingularDisplayName), ResourceType, x_ResourceType),\r\n ResourceType\r\n )\r\n //\r\n // Handle missing values\r\n | extend old_PublisherName = PublisherName, PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, '')\r\n //\r\n // Handle FOCUS 1.0-preview Region column\r\n | extend old_Region = Region\r\n | extend old_RegionId = RegionId, RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region))\r\n | extend RegionName = coalesce(RegionName, Region)\r\n //\r\n // SKU properties\r\n | extend x_SkuCoreCount = toint(coalesce(SkuPriceDetails.CoreCount, SkuPriceDetails.x_VCPUs, x_SkuDetails.VCPUs, SkuPriceDetails.x_VCores, x_SkuDetails.VCores, SkuPriceDetails.x_vCores, x_SkuDetails.vCores))\r\n | extend x_SkuInstanceType = tostring(coalesce(SkuPriceDetails.InstanceType, SkuPriceDetails.x_ServiceType, x_SkuDetails.ServiceType, SkuPriceDetails.x_ServerSku, x_SkuDetails.ServerSku))\r\n | extend x_SkuOperatingSystem = case(\r\n isnotempty(SkuPriceDetails.OperatingSystem), SkuPriceDetails.OperatingSystem,\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Canonical', 'Linux',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType)\r\n )\r\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\r\n | extend SkuPriceDetails = case(\r\n // FOCUS 1.2\r\n isnotempty(SkuPriceDetails), SkuPriceDetails,\r\n // FOCUS 1.0-preview, 1.0\r\n parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\r\n // Prefix all keys with x_ first to avoid double-prefixing\r\n , @'([\\{,])\"', @'\\1\"x_')\r\n // CoreCount for number of CPUs/vCPUs/cores/vCores\r\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\r\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\r\n // TODO: DiskSpace for disk size in GiB\r\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\r\n // TODO: GpuCount for the number of GPUs\r\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\r\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\r\n // TODO: InstanceSeries for the size family/series\r\n // TODO: MemorySize for the RAM in GiB\r\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\r\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\r\n // OperatingSystem for the OS name\r\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\r\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\r\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\r\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\r\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\r\n )\r\n )\r\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\r\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\r\n SkuPriceDetails)\r\n //\r\n // Azure Hybrid Benefit\r\n | extend tmp_SqlAhb = tolower(coalesce(x_SkuDetails.AHB, SkuPriceDetails.x_AHB))\r\n | extend x_SkuLicenseType = case(\r\n ChargeCategory != 'Usage', '',\r\n x_SkuMeterCategory in ('Virtual Machines', 'Virtual Machine Licenses') and (x_SkuMeterSubcategory contains 'Windows' or coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL'), 'Windows Server',\r\n isnotempty(tmp_SqlAhb) or x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\r\n ''\r\n )\r\n | extend x_SkuLicenseStatus = case(\r\n isempty(x_SkuLicenseType), '',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL' or tmp_SqlAhb == 'true' or x_SkuMeterSubcategory contains 'Azure Hybrid Benefit', 'Enabled',\r\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not Enabled',\r\n ''\r\n )\r\n | extend x_SkuLicenseQuantity = case(\r\n isempty(x_SkuCoreCount) or isempty(x_SkuLicenseType), int(null),\r\n x_SkuCoreCount <= 8, int(8),\r\n x_SkuCoreCount > 8, x_SkuCoreCount,\r\n int(null)\r\n )\r\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\r\n //\r\n // Savings\r\n | extend x_CommitmentDiscountSavings = iff(isempty(ContractedCost) or ContractedCost == 0 or ContractedCost - EffectiveCost < 0.0001, real(0), ContractedCost - EffectiveCost)\r\n | extend x_NegotiatedDiscountSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - ContractedCost < 0.0001, real(0), ListCost - ContractedCost)\r\n | extend x_TotalSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - EffectiveCost < 0.0001, real(0), ListCost - EffectiveCost)\r\n | extend x_CommitmentDiscountPercent = iff(isempty(ContractedUnitPrice) or ContractedUnitPrice == 0 or ContractedUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\r\n | extend x_NegotiatedDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - ContractedUnitPrice < 0.0001, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\r\n | extend x_TotalDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\r\n //\r\n // Minor fixes\r\n | extend old_BillingPeriodEnd = BillingPeriodEnd, BillingPeriodEnd = startofmonth(BillingPeriodEnd)\r\n | extend old_BillingPeriodStart = BillingPeriodStart, BillingPeriodStart = startofmonth(BillingPeriodStart)\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n CapacityReservationId,\r\n CapacityReservationStatus,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\r\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\r\n EffectiveCost,\r\n InvoiceId = coalesce(InvoiceId, x_InvoiceId),\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory, // TODO: Populate ServiceSubcategory from ServiceName when missing\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceDetails,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName = iff(isempty(SubAccountId), '', SubAccountName),\r\n SubAccountType,\r\n Tags,\r\n x_AccountId = iff(x_AccountId == '-2', '', x_AccountId),\r\n x_AccountName = iff(x_AccountId == '-2', '', x_AccountName),\r\n x_AccountOwnerId = iff(x_AccountId == '-2', '', x_AccountOwnerId),\r\n x_AmortizationClass,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\r\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\r\n ProviderName\r\n ),\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingItemCode,\r\n x_BillingItemName,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountPercent,\r\n x_CommitmentDiscountSavings,\r\n x_CommitmentDiscountSpendEligibility = '', // TODO: Add x_CommitmentDiscountSpendEligibility for Costs\r\n x_CommitmentDiscountUsageEligibility = '', // TODO: Add x_CommitmentDiscountUsageEligibility for Costs\r\n x_CommitmentDiscountUtilizationAmount,\r\n x_CommitmentDiscountUtilizationPotential,\r\n x_CommodityCode,\r\n x_CommodityName,\r\n x_ComponentName,\r\n x_ComponentType,\r\n x_ConsumedCoreHours,\r\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd),\r\n x_CostAllocationRuleName,\r\n x_CostCategories = parse_json(x_CostCategories),\r\n x_CostCenter,\r\n x_CostType,\r\n x_Credits = parse_json(x_Credits),\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount = parse_json(x_Discount),\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InstanceID,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId = case(\r\n x_InvoiceSectionId == '-2', '',\r\n x_InvoiceSectionId\r\n ),\r\n x_InvoiceSectionName = case(\r\n x_InvoiceSectionName == 'Unassigned', '',\r\n x_InvoiceSectionName\r\n ),\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_NegotiatedDiscountPercent,\r\n x_NegotiatedDiscountSavings,\r\n x_Operation,\r\n x_OwnerAccountID,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription = iff(x_PricingUnitDescription == 'Unassigned', '', x_PricingUnitDescription),\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName = tolower(x_ResourceGroupName),\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServiceModel, // TODO: Populate from ServiceName when missing\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuCoreCount,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuInstanceType,\r\n x_SkuIsCreditEligible,\r\n x_SkuLicenseQuantity,\r\n x_SkuLicenseStatus,\r\n x_SkuLicenseType,\r\n x_SkuLicenseUnit,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOperatingSystem,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuPlanName,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceValues = bag_merge(\r\n checkString('BillingPeriodEnd', old_BillingPeriodEnd, BillingPeriodEnd),\r\n checkString('BillingPeriodStart', old_BillingPeriodStart, BillingPeriodStart),\r\n checkString('CapacityReservationStatus', old_CapacityReservationStatus, CapacityReservationStatus),\r\n checkString('ChargeCategory', old_ChargeCategory, ChargeCategory),\r\n checkString('ChargeClass', old_ChargeClass, ChargeClass),\r\n checkString('ChargeSubcategory', old_ChargeSubcategory, ''), // Not included in final schema; use empty string\r\n checkString('ChargeFrequency', old_ChargeFrequency, ChargeFrequency),\r\n checkReal('CommitmentDiscountQuantity', old_CommitmentDiscountQuantity, CommitmentDiscountQuantity),\r\n checkString('CommitmentDiscountUnit', old_CommitmentDiscountUnit, CommitmentDiscountUnit),\r\n checkString('CommitmentDiscountStatus', old_CommitmentDiscountStatus, CommitmentDiscountStatus),\r\n checkReal('ConsumedQuantity', old_ConsumedQuantity, ConsumedQuantity),\r\n checkString('ConsumedUnit', old_ConsumedUnit, ConsumedUnit),\r\n checkReal('ContractedCost', old_ContractedCost, ContractedCost),\r\n checkReal('ContractedUnitPrice', old_ContractedUnitPrice, ContractedUnitPrice),\r\n checkReal('EffectiveCost', old_EffectiveCost, EffectiveCost),\r\n checkReal('ListCost', old_ListCost, ListCost),\r\n checkReal('ListUnitPrice', old_ListUnitPrice, ListUnitPrice),\r\n checkString('PricingCategory', old_PricingCategory, PricingCategory),\r\n checkReal('PricingQuantity', old_PricingQuantity, PricingQuantity),\r\n checkString('ProviderName', old_ProviderName, ProviderName),\r\n checkString('PublisherName', old_PublisherName, PublisherName),\r\n checkString('Region', old_Region, ''), // Not included in final schema; use empty string\r\n checkString('RegionId', old_RegionId, RegionId),\r\n checkString('ResourceId', old_ResourceId, ResourceId),\r\n checkString('ResourceName', old_ResourceName, ResourceName),\r\n checkString('ResourceType', old_ResourceType, ResourceType),\r\n checkString('x_AmortizationClass', old_x_AmortizationClass, x_AmortizationClass),\r\n checkReal('x_EffectiveCostInUsd', old_x_EffectiveCostInUsd, x_EffectiveCostInUsd),\r\n checkReal('x_EffectiveUnitPrice', old_x_EffectiveUnitPrice, x_EffectiveUnitPrice),\r\n checkString('x_ResourceType', old_x_ResourceType, x_ResourceType)\r\n ),\r\n x_SourceVersion,\r\n x_SubproductName,\r\n x_TotalDiscountPercent,\r\n x_TotalSavings,\r\n x_UsageType\r\n}\r\n\r\n// Costs_final_v1_2 table\r\n.create-merge table Costs_final_v1_2 (\r\n AvailabilityZone: string,\r\n BilledCost: real,\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingAccountType: string,\r\n BillingCurrency: string,\r\n BillingPeriodEnd: datetime,\r\n BillingPeriodStart: datetime,\r\n CapacityReservationId: string,\r\n CapacityReservationStatus: string,\r\n ChargeCategory: string,\r\n ChargeClass: string,\r\n ChargeDescription: string,\r\n ChargeFrequency: string,\r\n ChargePeriodEnd: datetime,\r\n ChargePeriodStart: datetime,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountId: string,\r\n CommitmentDiscountName: string,\r\n CommitmentDiscountQuantity: real,\r\n CommitmentDiscountStatus: string,\r\n CommitmentDiscountType: string,\r\n CommitmentDiscountUnit: string,\r\n ConsumedQuantity: real,\r\n ConsumedUnit: string,\r\n ContractedCost: real,\r\n ContractedUnitPrice: real,\r\n EffectiveCost: real,\r\n InvoiceId: string,\r\n InvoiceIssuerName: string,\r\n ListCost: real,\r\n ListUnitPrice: real,\r\n PricingCategory: string,\r\n PricingCurrency: string,\r\n PricingQuantity: real,\r\n PricingUnit: string,\r\n ProviderName: string,\r\n PublisherName: string,\r\n RegionId: string,\r\n RegionName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n ServiceCategory: string,\r\n ServiceName: string,\r\n ServiceSubcategory: string,\r\n SkuId: string,\r\n SkuMeter: string,\r\n SkuPriceDetails: dynamic,\r\n SkuPriceId: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n SubAccountType: string,\r\n Tags: dynamic,\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_AmortizationClass: string, // Azure 1.2-preview+\r\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingItemCode: string, // Alibaba 1.0\r\n x_BillingItemName: string, // Alibaba 1.0\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_CommitmentDiscountNormalizedRatio: real, // Azure 1.2-preview+\r\n x_CommitmentDiscountPercent: real, // Hubs add-on\r\n x_CommitmentDiscountSavings: real, // Hubs add-on\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUtilizationAmount: real, // Hubs add-on\r\n x_CommitmentDiscountUtilizationPotential: real, // Hubs add-on\r\n x_CommodityCode: string, // Alibaba 1.0\r\n x_CommodityName: string, // Alibaba 1.0\r\n x_ComponentName: string, // Tencent 1.0\r\n x_ComponentType: string, // Tencent 1.0\r\n x_ConsumedCoreHours: real, // Hubs add-on\r\n x_ContractedCostInUsd: real, // Azure 1.0+\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_CostType: string, // GCP Jan 2024\r\n x_Credits: dynamic, // GCP Jan 2024\r\n x_CurrencyConversionRate: real, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: dynamic, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_InstanceID: string, // Alibaba 1.0\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_NegotiatedDiscountPercent:real, // Hubs add-on\r\n x_NegotiatedDiscountSavings:real, // Hubs add-on\r\n x_Operation: string, // AWS 1.0\r\n x_OwnerAccountID: string, // Tencent 1.0\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServiceModel: string, // Azure 1.2-preview+\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuCoreCount: int, // Hubs add-on\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\r\n x_SkuInstanceType: string, // Hubs add-on\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuLicenseQuantity: int, // Hubs add-on\r\n x_SkuLicenseStatus: string, // Hubs add-on\r\n x_SkuLicenseType: string, // Hubs add-on\r\n x_SkuLicenseUnit: string, // Hubs add-on\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOperatingSystem: string, // Hubs add-on\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuPlanName: string, // Azure 1.2-preview+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceChanges: string, // Hubs add-on\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceValues: dynamic, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubproductName: string, // Tencent 1.0\r\n x_TotalDiscountPercent: real, // Hubs add-on\r\n x_TotalSavings: real, // Hubs add-on\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Update policy for Costs_raw -> Costs_final_v1_2 table\r\n.alter table Costs_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Costs_raw\",\r\n \"Query\": \"Costs_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Actual costs |===================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\r\nActualCosts_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n ActualCosts_raw\r\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodEnd = Date + 1d,\r\n ChargePeriodStart = Date,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId = '',\r\n SkuMeter = MeterName,\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentType = '',\r\n x_ComponentName = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = '',\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel,\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for ActualCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"ActualCosts_raw\",\r\n \"Query\": \"ActualCosts_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Amortized costs |================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\r\nAmortizedCosts_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n AmortizedCosts_raw\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodEnd = Date + 1d,\r\n ChargePeriodStart = Date,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId = '',\r\n SkuMeter = MeterName,\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentType = '',\r\n x_ComponentName = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = '',\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel,\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for AmortizedCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"AmortizedCosts_raw\",\r\n \"Query\": \"AmortizedCosts_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All commitment discount usage transformed to FOCUS 1.2. This includes reservationdeatils_raw.', folder='Commitment discounts')\r\nCommitmentDiscountUsage_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n CommitmentDiscountUsage_raw\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Handle resource columns\r\n | extend ResourceId = tolower(InstanceId)\r\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\r\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\r\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\r\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\r\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\r\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n ChargePeriodEnd = UsageDate + 1d,\r\n ChargePeriodStart = UsageDate,\r\n CommitmentDiscountCategory = 'Usage',\r\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\r\n CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\r\n CommitmentDiscountType = 'Reservation',\r\n CommitmentDiscountUnit = case(\r\n InstanceFlexibilityRatio == 1, 'Hours',\r\n InstanceFlexibilityRatio != 1, 'Normalized Hours',\r\n ''\r\n ),\r\n ConsumedQuantity = UsedHours,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\r\n x_CommitmentDiscountCommittedAmount = ReservedHours,\r\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\r\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\r\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\r\n x_IngestionTime = ingestion_time(),\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n // x_RowId = hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // CommitmentDiscountId,\r\n // ResourceId,\r\n // ChargePeriodStart\r\n // )),\r\n x_ServiceModel,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\r\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\r\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\r\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\r\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\r\n}\r\n\r\n// CommitmentDiscountUsage_final_v1_2 table\r\n.create-merge table CommitmentDiscountUsage_final_v1_2 (\r\n ChargePeriodEnd: datetime, // Hubs add-on\r\n ChargePeriodStart: datetime, // MS 2023-03-01\r\n CommitmentDiscountCategory: string, // Hubs add-on\r\n CommitmentDiscountId: string, // MS 2023-03-01\r\n CommitmentDiscountQuantity: real, // MS 2023-03-01\r\n CommitmentDiscountType: string, // Hubs add-on\r\n CommitmentDiscountUnit: string, // Hubs add-on\r\n ConsumedQuantity: real, // MS 2023-03-01\r\n ProviderName: string, // Hubs add-on\r\n ResourceId: string, // MS 2023-03-01\r\n ResourceName: string, // Hubs add-on\r\n ResourceType: string, // Hubs add-on\r\n ServiceCategory: string, // Hubs add-on\r\n ServiceName: string, // Hubs add-on\r\n ServiceSubcategory: string, // Hubs add-on\r\n SubAccountId: string, // Hubs add-on\r\n x_CommitmentDiscountCommittedCount: real, // MS 2023-03-01\r\n x_CommitmentDiscountCommittedAmount: real, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedRatio: real, // MS 2023-03-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_ResourceGroupName: string, // Hubs add-on\r\n x_ResourceType: string, // Hubs add-on\r\n x_ServiceModel: string, // Hubs add-on\r\n x_SkuOrderId: string, // MS 2023-03-01\r\n x_SkuSize: string, // MS 2023-03-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string // Hubs add-on\r\n)\r\n\r\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_2 table\r\n.alter table CommitmentDiscountUsage_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"CommitmentDiscountUsage_raw\",\r\n \"Query\": \"CommitmentDiscountUsage_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All recommendations transformed to FOCUS 1.2.', folder='Recommendations')\r\nRecommendations_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Recommendations_raw\r\n | extend x_IngestionTime = ingestion_time()\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Convert JSON cost columns to real\r\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\r\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\r\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\r\n //\r\n // Build recommendation details\r\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\r\n | extend x_RecommendationDetails = case(\r\n // Use incoming x_RecommendationDetails first\r\n isnotempty(x_RecommendationDetails), x_RecommendationDetails,\r\n // Create one for reservation recommendations if needed\r\n x_SourceType == 'ReservationRecommendations', bag_pack(\r\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\r\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\r\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\r\n 'CommitmentDiscountResourceType', ResourceType,\r\n 'CommitmentDiscountScope', Scope,\r\n 'LookbackPeriodDuration', case(\r\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\r\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\r\n ''\r\n ),\r\n 'LookbackPeriodStart', FirstUsageDate,\r\n 'RecommendedQuantity', RecommendedQuantity,\r\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\r\n 'RegionId', Location,\r\n 'RegionName', RegionName,\r\n 'SkuMeterId', MeterId,\r\n 'SkuPriceDetails', SkuProperties,\r\n 'SkuSize', coalesce(SKU, SkuName),\r\n 'SkuTerm', isoMonths(Term)\r\n ),\r\n dynamic({})\r\n )\r\n //\r\n // Prefer specified date, then fall back to generating a date based on reservation recommendation lookback period, then validate to ensure it's not in the future\r\n | extend x_RecommendationDate = coalesce(x_RecommendationDate, FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d))\r\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\r\n //\r\n | project\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n SubAccountId = coalesce(SubAccountId, iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), '')),\r\n SubAccountName,\r\n x_EffectiveCostAfter = coalesce(x_EffectiveCostAfter, TotalCostWithReservedInstances),\r\n x_EffectiveCostBefore = coalesce(x_EffectiveCostBefore, CostWithNoReservedInstances),\r\n x_EffectiveCostSavings = coalesce(x_EffectiveCostSavings, NetSavings),\r\n x_IngestionTime,\r\n x_RecommendationCategory, // TODO: Set for reservation recommendations\r\n x_RecommendationDate,\r\n x_RecommendationDescription,\r\n x_RecommendationDetails,\r\n x_RecommendationId, // TODO: Set for reservation recommendations\r\n x_ResourceGroupName,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n// Recommendations_final_v1_2 table\r\n.create-merge table Recommendations_final_v1_2 (\r\n ProviderName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n x_EffectiveCostAfter: real,\r\n x_EffectiveCostBefore: real,\r\n x_EffectiveCostSavings: real,\r\n x_IngestionTime: datetime,\r\n x_RecommendationCategory: string,\r\n x_RecommendationDate: datetime,\r\n x_RecommendationDescription: string,\r\n x_RecommendationDetails: dynamic,\r\n x_RecommendationId: string,\r\n x_ResourceGroupName: string,\r\n x_SourceName: string,\r\n x_SourceProvider: string,\r\n x_SourceType: string,\r\n x_SourceVersion: string\r\n)\r\n\r\n// Update policy for Recommendations_raw -> Recommendations_final_v1_2 table\r\n.alter table Recommendations_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Recommendations_raw\",\r\n \"Query\": \"Recommendations_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All transactions transformed to FOCUS 1.2.', folder='Transactions')\r\nTransactions_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Transactions_raw\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Handle BillingPeriodStart/End\r\n | extend BillingMonth = tostring(BillingMonth)\r\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\r\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n BilledCost = Amount,\r\n BillingAccountId = case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\r\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\r\n ''\r\n ),\r\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\r\n BillingCurrency = Currency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory = case(\r\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = case(\r\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\r\n EventType == 'Refund', 'Correction',\r\n ''\r\n ),\r\n ChargeDescription = Description,\r\n ChargeFrequency = case(\r\n BillingFrequency == 'OneTime', 'One-Time',\r\n BillingFrequency == 'Recurring', 'Recurring',\r\n BillingFrequency\r\n ),\r\n ChargePeriodStart = EventDate,\r\n InvoiceId,\r\n PricingQuantity = Quantity,\r\n PricingUnit = 'Reservations',\r\n ProviderName,\r\n RegionId = Region,\r\n RegionName = Region,\r\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\r\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerEmail,\r\n x_CostCenter = CostCenter,\r\n x_InvoiceNumber = Invoice,\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\r\n x_IngestionTime = ingestion_time(),\r\n x_MonetaryCommitment = MonetaryCommitment,\r\n x_Overage = Overage,\r\n x_PurchasingBillingAccountId = PurchasingEnrollment,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuOrderName = ReservationOrderName,\r\n x_SkuSize = ArmSkuName,\r\n x_SkuTerm = isoMonths(Term),\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId = PurchasingSubscriptionGuid,\r\n x_TransactionType = EventType\r\n}\r\n\r\n// Transactions_final_v1_2 table\r\n.create-merge table Transactions_final_v1_2 (\r\n BilledCost: real, // MS CM EA+MCA 2023-05-01\r\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\r\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\r\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n ChargeCategory: string, // Hubs add-on\r\n ChargeClass: string, // Hubs add-on\r\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\r\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\r\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n InvoiceId: string, // MS CM MCA 2023-05-01\r\n PricingQuantity: real, // MS CM EA+MCA 2023-05-01\r\n PricingUnit: string, // Hubs add-on\r\n ProviderName: string, // Hubs add-on\r\n RegionId: string, // MS CM EA+MCA 2023-05-01\r\n RegionName: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\r\n x_AccountName: string, // MS CM EA 2023-05-01\r\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\r\n x_CostCenter: string, // MS CM EA 2023-05-01\r\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_MonetaryCommitment: real, // MS CM EA 2023-05-01\r\n x_Overage: real, // MS CM EA 2023-05-01\r\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\r\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\r\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\r\n)\r\n\r\n// Update policy for Transactions_raw -> Transactions_final_v1_2 table\r\n.alter table Transactions_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Transactions_raw\",\r\n \"Query\": \"Transactions_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n", + "$fxv#11": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Common utility functions\r\n//\r\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\r\n//======================================================================================================================\r\n\r\n\r\n//===| Date functions |=================================================================================================\r\n\r\n// monthstring\r\n.create-or-alter function \r\nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \r\nmonthstring(['date']: datetime, length: int = 9)\r\n{\r\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\r\n}\r\n\r\n// datestring\r\n.create-or-alter function \r\nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \r\ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n let month = (d: datetime) { monthstring(d, 3) };\r\n let endDate = iff(end == datetime('0001-01-01'), start, end);\r\n let sameDate = startofday(start) == startofday(endDate);\r\n let sameMonth = startofmonth(start) == startofmonth(endDate);\r\n let sameYear = startofyear(start) == startofyear(endDate);\r\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\r\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\r\n let currentYear = sameYear and startofyear(start) == startofyear(now());\r\n case(\r\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\r\n fullYear,\r\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\r\n // 1 full mo, same year | Mmm yyyy\r\n fullMonth and sameMonth and sameYear,\r\n strcat(month(start), ' ', getyear(start)),\r\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\r\n fullMonth and sameYear,\r\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\r\n fullMonth and not(sameYear),\r\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\r\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\r\n sameDate,\r\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\r\n not(fullMonth) and sameMonth and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\r\n not(fullMonth) and not(sameMonth) and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\r\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\r\n )\r\n}\r\n\r\n// daterange\r\n.create-or-alter function \r\nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \r\ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n datestring(start, end)\r\n}\r\n\r\n// monthsago\r\n.create-or-alter function \r\nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\r\nmonthsago(months: int)\r\n{\r\n datetime_add('month', -months, startofmonth(now()))\r\n}\r\n\r\n\r\n//===| Number functions |===============================================================================================\r\n// NOTE: Must be defined before string converters\r\n\r\n// delta\r\n.create-or-alter function \r\nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \r\ndelta(oldval: double, newval: double)\r\n{\r\n (newval - todouble(oldval))/oldval\r\n}\r\n\r\n// percentOfTotal\r\n// NOTE: Must be before percent() function\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercentOfTotal(t: (Count: long), tot: long)\r\n{\r\n let total = todouble(tot);\r\n t \r\n | extend Percent = round(Count / total * 100, 3) \r\n | order by Count desc\r\n}\r\n\r\n// percent\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercent(t: (Count: long))\r\n{\r\n let total = todouble(toscalar(t | summarize sum(Count)));\r\n percentOfTotal(t, total)\r\n}\r\n\r\n// plusminus\r\n.create-or-alter function \r\nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\r\nplusminus(val: string)\r\n{\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, val, strcat('+', val))\r\n}\r\n\r\n// updown\r\n.create-or-alter function \r\nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\r\nupdown(val: string)\r\n{\r\n // TODO: Handle 0\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\r\n}\r\n\r\n\r\n//===| String functions |===============================================================================================\r\n\r\n// percentstring\r\n// NOTE: Must be defined before deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\r\npercentstring(num: double, total: double = 1.0, places: int = 9)\r\n{\r\n let value = 1.0 * num / total * 100;\r\n strcat(case(\r\n places != 9, round(value, places),\r\n value < 10, round(value, 2),\r\n round(value, 1)\r\n ), '%')\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// arraystring\r\n.create-or-alter function \r\nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\r\narraystring(arr: dynamic)\r\n{\r\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\r\n tostring(arr)\r\n , @'^\\[\"', '')\r\n , @'\"\\]$', '')\r\n , @'^, ', '')\r\n , @', $', '')\r\n , @'^\\[]$', '')\r\n , '\",\"', ', ')\r\n}\r\n\r\n// deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\r\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\r\n{\r\n let d = delta(oldval, newval);\r\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\r\n}\r\n\r\n// diffstring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\r\ndiffstring(oldval: double, newval: double, places: int = 1)\r\n{\r\n plusminus(round(newval - oldval, places))\r\n}\r\n\r\n// numberstring\r\n.create-or-alter function \r\nwith (docstring = 'Convert a number to a string', folder = 'Common')\r\nnumberstring(num: double, abbrev: bool = true)\r\n{\r\n replace_regex(case(\r\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\r\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\r\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\r\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\r\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\r\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\r\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\r\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\r\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\r\n tostring(num)\r\n ), @'\\.0$', '')\r\n}\r\n\r\n\r\n//===| Other |==========================================================================================================\r\n\r\n// ifempty\r\n.create-or-alter function \r\nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\r\nifempty(val: dynamic, defaultVal: dynamic)\r\n{\r\n iff(isempty(val), defaultVal, val)\r\n}\r\n", + "$fxv#12": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / Open data functions\r\n// Wrap Ingestion database tables for easy access.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// PricingUnits\r\n.create-or-alter function\r\nwith (docstring = 'Gets pricing units from the FinOps toolkit PricingUnits open data.', folder = 'OpenData')\r\nPricingUnits()\r\n{\r\n database('Ingestion').PricingUnits\r\n}\r\n\r\n// Regions\r\n.create-or-alter function\r\nwith (docstring = 'Gets regions from the FinOps toolkit Regions open data.', folder = 'OpenData')\r\nRegion()\r\n{\r\n database('Ingestion').Regions\r\n}\r\n\r\n// ResourceTypes\r\n.create-or-alter function\r\nwith (docstring = 'Gets resource types from the FinOps toolkit ResourceTypes open data.', folder = 'OpenData')\r\nResourceType()\r\n{\r\n database('Ingestion').ResourceTypes\r\n}\r\n\r\n// Services\r\n.create-or-alter function\r\nwith (docstring = 'Gets services from the FinOps toolkit Services open data.', folder = 'OpenData')\r\nServices()\r\n{\r\n database('Ingestion').Services\r\n}\r\n", + "$fxv#13": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / FOCUS 1.0 functions\r\n// Used for reporting with backward compatibility.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// CommitmentDiscountUsage_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.0.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage_v1_0()\r\n{\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\r\n | union (\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n x_CommitmentDiscountCommittedCount = todecimal(x_CommitmentDiscountCommittedCount),\r\n x_CommitmentDiscountCommittedAmount = todecimal(x_CommitmentDiscountCommittedAmount),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio)\r\n )\r\n | project\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount,\r\n x_CommitmentDiscountCommittedAmount,\r\n x_CommitmentDiscountNormalizedGroup,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountQuantity,\r\n x_IngestionTime,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceModel,\r\n x_SkuOrderId,\r\n x_SkuSize,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Costs_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.0.', folder = 'Costs')\r\nCosts_v1_0()\r\n{\r\n database('Ingestion').Costs_final_v1_0\r\n | union (\r\n database('Ingestion').Costs_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n ContractedCost = todecimal(ContractedCost),\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n EffectiveCost = todecimal(EffectiveCost),\r\n ListCost = todecimal(ListCost),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\r\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\r\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\r\n // Rename columns\r\n | project-rename\r\n x_InvoiceId = InvoiceId,\r\n x_PricingCurrency = PricingCurrency,\r\n x_SkuMeterName = SkuMeter\r\n // Generate historical x_SkuDetails format from SkuPriceDetails\r\n | extend x_SkuDetails = iff(isnotempty(x_SkuDetails), x_SkuDetails, parse_json(replace_regex(tostring(SkuPriceDetails), @'([\\{,])\"x_', @'\\1\"')))\r\n )\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost,\r\n ContractedUnitPrice,\r\n EffectiveCost,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SkuId,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType,\r\n Tags,\r\n x_AccountId,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_ContractedCostInUsd,\r\n x_CostAllocationRuleName,\r\n x_CostCategories,\r\n x_CostCenter,\r\n x_Credits,\r\n x_CostType,\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount,\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InvoiceId,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_Operation,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingCurrency,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuIsCreditEligible,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_UsageType\r\n}\r\n\r\n\r\n// Prices_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices aligned to FOCUS 1.0.', folder = 'Prices')\r\nPrices_v1_0()\r\n{\r\n database('Ingestion').Prices_final_v1_0\r\n | union (\r\n database('Ingestion').Prices_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n x_BaseUnitPrice = todecimal(x_BaseUnitPrice),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\r\n x_ContractedUnitPriceDiscount = todecimal(x_ContractedUnitPriceDiscount),\r\n x_ContractedUnitPriceDiscountPercent = todecimal(x_ContractedUnitPriceDiscountPercent),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_EffectiveUnitPriceDiscount = todecimal(x_EffectiveUnitPriceDiscount),\r\n x_EffectiveUnitPriceDiscountPercent = todecimal(x_EffectiveUnitPriceDiscountPercent),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize),\r\n x_SkuIncludedQuantity = todecimal(x_SkuIncludedQuantity),\r\n x_SkuTier = todecimal(x_SkuTier),\r\n x_TotalUnitPriceDiscount = todecimal(x_TotalUnitPriceDiscount),\r\n x_TotalUnitPriceDiscountPercent = todecimal(x_TotalUnitPriceDiscountPercent) \r\n // Rename columns\r\n | project-rename\r\n x_PricingCurrency = PricingCurrency,\r\n x_SkuMeterName = SkuMeter\r\n )\r\n | project\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType,\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingUnit,\r\n SkuId,\r\n SkuPriceId,\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent,\r\n x_EffectivePeriodEnd,\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingCurrency,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_SkuDescription,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent\r\n}\r\n\r\n\r\n// Recommendations_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.0.', folder = 'Recommendations')\r\nRecommendations_v1_0()\r\n{\r\n database('Ingestion').Recommendations_final_v1_0\r\n | union (\r\n database('Ingestion').Recommendations_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n x_EffectiveCostAfter = todecimal(x_EffectiveCostAfter),\r\n x_EffectiveCostBefore = todecimal(x_EffectiveCostBefore),\r\n x_EffectiveCostSavings = todecimal(x_EffectiveCostSavings)\r\n )\r\n | project\r\n ProviderName,\r\n SubAccountId,\r\n x_IngestionTime,\r\n x_EffectiveCostAfter,\r\n x_EffectiveCostBefore,\r\n x_EffectiveCostSavings,\r\n x_RecommendationDate,\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Transactions_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.0.', folder = 'Transactions')\r\nTransactions_v1_0()\r\n{\r\n database('Ingestion').Transactions_final_v1_0\r\n | union (\r\n database('Ingestion').Transactions_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n x_MonetaryCommitment = todecimal(x_MonetaryCommitment),\r\n x_Overage = todecimal(x_Overage)\r\n // Rename columns\r\n | project-rename\r\n x_InvoiceId = InvoiceId\r\n )\r\n | project\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodStart,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n RegionId,\r\n RegionName,\r\n SubAccountId,\r\n SubAccountName,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_CostCenter,\r\n x_InvoiceId,\r\n x_InvoiceNumber,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_IngestionTime,\r\n x_MonetaryCommitment,\r\n x_Overage,\r\n x_PurchasingBillingAccountId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuSize,\r\n x_SkuTerm,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId,\r\n x_TransactionType\r\n}\r\n", + "$fxv#14": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / FOCUS 1.2 functions\r\n// Used for reporting with backward compatibility.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// CommitmentDiscountUsage_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.2.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage_v1_2()\r\n{\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\r\n | union (\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n ConsumedQuantity = toreal(ConsumedQuantity),\r\n x_CommitmentDiscountCommittedCount = toreal(x_CommitmentDiscountCommittedCount),\r\n x_CommitmentDiscountCommittedAmount = toreal(x_CommitmentDiscountCommittedAmount),\r\n x_CommitmentDiscountNormalizedRatio = toreal(x_CommitmentDiscountNormalizedRatio)\r\n // Add new columns\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceSubcategory) on x_ResourceType\r\n | extend CommitmentDiscountQuantity = ConsumedQuantity * x_CommitmentDiscountNormalizedRatio\r\n | extend CommitmentDiscountUnit = case(\r\n x_CommitmentDiscountNormalizedRatio == 1, 'Hours',\r\n x_CommitmentDiscountNormalizedRatio > 1, 'Normalized Hours',\r\n ''\r\n )\r\n )\r\n | project\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount,\r\n x_CommitmentDiscountCommittedAmount,\r\n x_CommitmentDiscountNormalizedGroup,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_IngestionTime,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceModel,\r\n x_SkuOrderId,\r\n x_SkuSize,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Costs_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.2.', folder = 'Costs')\r\nCosts_v1_2()\r\n{\r\n database('Ingestion').Costs_final_v1_2\r\n | union (\r\n database('Ingestion').Costs_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n BilledCost = toreal(BilledCost),\r\n ConsumedQuantity = toreal(ConsumedQuantity),\r\n ContractedCost = toreal(ContractedCost),\r\n ContractedUnitPrice = toreal(ContractedUnitPrice),\r\n EffectiveCost = toreal(EffectiveCost),\r\n ListCost = toreal(ListCost),\r\n ListUnitPrice = toreal(ListUnitPrice),\r\n PricingQuantity = toreal(PricingQuantity),\r\n x_BilledCostInUsd = toreal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = toreal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = toreal(x_BillingExchangeRate),\r\n x_ContractedCostInUsd = toreal(x_ContractedCostInUsd),\r\n x_CurrencyConversionRate = toreal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = toreal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = toreal(x_ListCostInUsd),\r\n x_PricingBlockSize = toreal(x_PricingBlockSize)\r\n // Rename columns\r\n | project-rename\r\n InvoiceId = x_InvoiceId,\r\n PricingCurrency = x_PricingCurrency,\r\n SkuMeter = x_SkuMeterName\r\n // Add new columns\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | extend CapacityReservationId = tostring(x_SkuDetails.VMCapacityReservationId)\r\n | extend CapacityReservationStatus = case(\r\n isempty(CapacityReservationId), '',\r\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\r\n 'Used'\r\n )\r\n | extend x_CommitmentDiscountNormalizedRatio = case(\r\n // Not applicable\r\n isempty(CommitmentDiscountStatus), real(null),\r\n // Parse from SKU details if not specified explicitly\r\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, dynamic(1)))\r\n )\r\n | extend CommitmentDiscountQuantity = case(\r\n isempty(CommitmentDiscountStatus), real(null),\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\r\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\r\n real(null)\r\n )\r\n | extend CommitmentDiscountUnit = case(\r\n isempty(CommitmentDiscountQuantity), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\r\n ''\r\n )\r\n | extend x_AmortizationClass = case(\r\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\r\n ''\r\n )\r\n // Hubs add-ons\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n | extend x_SkuCoreCount = toint(coalesce(x_SkuDetails.VCPUs, x_SkuDetails.VCores, x_SkuDetails.vCores))\r\n | extend x_SkuInstanceType = tostring(coalesce(x_SkuDetails.ServiceType, x_SkuDetails.ServerSku))\r\n | extend x_SkuOperatingSystem = case(\r\n x_SkuDetails.ImageType == 'Canonical', 'Linux',\r\n x_SkuDetails.ImageType == 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\r\n x_SkuDetails.ImageType\r\n )\r\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\r\n | extend tmp_SqlAhb = tolower(x_SkuDetails.AHB)\r\n | extend x_SkuLicenseType = case(\r\n x_SkuDetails.ImageType contains 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\r\n ''\r\n )\r\n | extend x_SkuLicenseStatus = case(\r\n isnotempty(x_SkuLicenseType) or tmp_SqlAhb == 'true' or (x_SkuMeterSubcategory contains 'Azure Hybrid Benefit'), 'Enabled',\r\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not enabled',\r\n ''\r\n )\r\n | extend x_SkuLicenseQuantity = case(\r\n isempty(x_SkuCoreCount), int(null),\r\n x_SkuCoreCount <= 8, int(8),\r\n x_SkuCoreCount > 8, x_SkuCoreCount,\r\n int(null)\r\n )\r\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\r\n | extend x_CommitmentDiscountSavings = iff(ContractedCost < EffectiveCost, real(0), ContractedCost - EffectiveCost)\r\n | extend x_NegotiatedDiscountSavings = iff(ListCost < ContractedCost, real(0), ListCost - ContractedCost)\r\n | extend x_TotalSavings = iff(ListCost < EffectiveCost, real(0), ListCost - EffectiveCost)\r\n | extend x_CommitmentDiscountPercent = iff(ContractedUnitPrice == 0, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\r\n | extend x_NegotiatedDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\r\n | extend x_TotalDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\r\n // SkuPriceDetails conversion -- Must be after hubs add-ons\r\n | extend SkuPriceDetails = parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\r\n // Prefix all keys with x_ first to avoid double-prefixing\r\n , @'([\\{,])\"', @'\\1\"x_')\r\n // CoreCount for number of CPUs/vCPUs/cores/vCores\r\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\r\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\r\n // TODO: DiskSpace for disk size in GiB\r\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\r\n // TODO: GpuCount for the number of GPUs\r\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\r\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\r\n // TODO: InstanceSeries for the size family/series\r\n // TODO: MemorySize for the RAM in GiB\r\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\r\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\r\n // OperatingSystem for the OS name\r\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\r\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\r\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\r\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\r\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\r\n )\r\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\r\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\r\n SkuPriceDetails)\r\n )\r\n | extend SkuPriceDetails = iff(isnotempty(SkuPriceDetails), SkuPriceDetails, parse_json(replace_regex(tostring(x_SkuDetails), @'([\\{,])\"', @'\\1\"x_')))\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n CapacityReservationId,\r\n CapacityReservationStatus,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost,\r\n ContractedUnitPrice,\r\n EffectiveCost,\r\n InvoiceId,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceDetails,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType,\r\n Tags,\r\n x_AccountId,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_AmortizationClass,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingItemCode,\r\n x_BillingItemName,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountPercent,\r\n x_CommitmentDiscountSavings,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_CommitmentDiscountUtilizationAmount,\r\n x_CommitmentDiscountUtilizationPotential,\r\n x_CommodityCode,\r\n x_CommodityName,\r\n x_ComponentName,\r\n x_ComponentType,\r\n x_ConsumedCoreHours,\r\n x_ContractedCostInUsd,\r\n x_CostAllocationRuleName,\r\n x_CostCategories,\r\n x_CostCenter,\r\n x_CostType,\r\n x_Credits,\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount,\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InstanceID,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_NegotiatedDiscountPercent,\r\n x_NegotiatedDiscountSavings,\r\n x_Operation,\r\n x_OwnerAccountID,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServiceModel,\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuCoreCount,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuInstanceType,\r\n x_SkuIsCreditEligible,\r\n x_SkuLicenseQuantity,\r\n x_SkuLicenseStatus,\r\n x_SkuLicenseType,\r\n x_SkuLicenseUnit,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOperatingSystem,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuPlanName,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceValues,\r\n x_SourceVersion,\r\n x_SubproductName,\r\n x_TotalDiscountPercent,\r\n x_TotalSavings,\r\n x_UsageType\r\n}\r\n\r\n\r\n// Prices_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices aligned to FOCUS 1.2.', folder = 'Prices')\r\nPrices_v1_2()\r\n{\r\n database('Ingestion').Prices_final_v1_2\r\n | union (\r\n database('Ingestion').Prices_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n ContractedUnitPrice = toreal(ContractedUnitPrice),\r\n ListUnitPrice = toreal(ListUnitPrice),\r\n x_BaseUnitPrice = toreal(x_BaseUnitPrice),\r\n x_ContractedUnitPriceDiscount = toreal(x_ContractedUnitPriceDiscount),\r\n x_ContractedUnitPriceDiscountPercent = toreal(x_ContractedUnitPriceDiscountPercent),\r\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\r\n x_EffectiveUnitPriceDiscount = toreal(x_EffectiveUnitPriceDiscount),\r\n x_EffectiveUnitPriceDiscountPercent = toreal(x_EffectiveUnitPriceDiscountPercent),\r\n x_PricingBlockSize = toreal(x_PricingBlockSize),\r\n x_SkuIncludedQuantity = toreal(x_SkuIncludedQuantity),\r\n x_SkuTier = toreal(x_SkuTier),\r\n x_TotalUnitPriceDiscount = toreal(x_TotalUnitPriceDiscount),\r\n x_TotalUnitPriceDiscountPercent = toreal(x_TotalUnitPriceDiscountPercent) \r\n // Rename columns\r\n | project-rename\r\n PricingCurrency = x_PricingCurrency,\r\n SkuMeter = x_SkuMeterName\r\n )\r\n | project\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingUnit,\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceId,\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent,\r\n x_EffectivePeriodEnd,\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_SkuDescription,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent\r\n}\r\n\r\n\r\n// Recommendations_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.2.', folder = 'Recommendations')\r\nRecommendations_v1_2()\r\n{\r\n database('Ingestion').Recommendations_final_v1_2\r\n | union (\r\n database('Ingestion').Recommendations_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n x_EffectiveCostAfter = toreal(x_EffectiveCostAfter),\r\n x_EffectiveCostBefore = toreal(x_EffectiveCostBefore),\r\n x_EffectiveCostSavings = toreal(x_EffectiveCostSavings)\r\n )\r\n | project\r\n ProviderName,\r\n SubAccountId,\r\n x_IngestionTime,\r\n x_EffectiveCostAfter,\r\n x_EffectiveCostBefore,\r\n x_EffectiveCostSavings,\r\n x_RecommendationDate,\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Transactions_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.2.', folder = 'Transactions')\r\nTransactions_v1_2()\r\n{\r\n database('Ingestion').Transactions_final_v1_2\r\n | union (\r\n database('Ingestion').Transactions_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n BilledCost = toreal(BilledCost),\r\n PricingQuantity = toreal(PricingQuantity),\r\n x_MonetaryCommitment = toreal(x_MonetaryCommitment),\r\n x_Overage = toreal(x_Overage)\r\n // Rename columns\r\n | project-rename\r\n InvoiceId = x_InvoiceId\r\n )\r\n | project\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodStart,\r\n InvoiceId,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n RegionId,\r\n RegionName,\r\n SubAccountId,\r\n SubAccountName,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_CostCenter,\r\n x_InvoiceNumber,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_IngestionTime,\r\n x_MonetaryCommitment,\r\n x_Overage,\r\n x_PurchasingBillingAccountId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuSize,\r\n x_SkuTerm,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId,\r\n x_TransactionType\r\n}\r\n\r\n\r\n//======================================================================================================================\r\n// Latest FOCUS version\r\n//======================================================================================================================\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage()\r\n{\r\n CommitmentDiscountUsage_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\r\nCosts()\r\n{\r\n Costs_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\r\nPrices()\r\n{\r\n Prices_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\r\nRecommendations()\r\n{\r\n Recommendations_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\r\nTransactions()\r\n{\r\n Transactions_v1_2()\r\n}\r\n", + "$fxv#15": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / Latest FOCUS version functions\r\n// Used for ad hoc queries.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage()\r\n{\r\n CommitmentDiscountUsage_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\r\nCosts()\r\n{\r\n Costs_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\r\nPrices()\r\n{\r\n Prices_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\r\nRecommendations()\r\n{\r\n Recommendations_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\r\nTransactions()\r\n{\r\n Transactions_v1_2()\r\n}\r\n", + "$fxv#2": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_3(id: string) {\r\n dynamic({\r\n \"microsoft.hybridnetwork/vendors\": { \"SingularDisplayName\": \"Azure Network Function Manager ? vendor\" }\r\n ,\"microsoft.hybridonboarding/extensionmanagers\": { \"SingularDisplayName\": \"Microsoft.HybridOnboarding extension manager\" }\r\n ,\"microsoft.impact/connectors\": { \"SingularDisplayName\": \"Impact Reporting Connector\" }\r\n ,\"microsoft.impact/impactcategories\": { \"SingularDisplayName\": \"Microsoft.Impact impact category\" }\r\n ,\"microsoft.impact/topologyimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact topology impact\" }\r\n ,\"microsoft.impact/workloadimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact workload impact\" }\r\n ,\"microsoft.impact/workloadimpacts/insights\": { \"SingularDisplayName\": \"Microsoft.Impact workload impacts insight\" }\r\n ,\"microsoft.importexport/jobs\": { \"SingularDisplayName\": \"Microsoft.ImportExport job\" }\r\n ,\"microsoft.insights/actiongroups\": { \"SingularDisplayName\": \"Action group\" }\r\n ,\"microsoft.insights/activitylogalerts\": { \"SingularDisplayName\": \"Activity log alert rule\" }\r\n ,\"microsoft.insights/alertrules\": { \"SingularDisplayName\": \"Microsoft.Insights alertrule\" }\r\n ,\"microsoft.insights/alertrules/incidents\": { \"SingularDisplayName\": \"Microsoft.insights alertrules incident\" }\r\n ,\"microsoft.insights/autoscalesettings\": { \"SingularDisplayName\": \"Microsoft.Insights autoscalesetting\" }\r\n ,\"microsoft.insights/components\": { \"SingularDisplayName\": \"Application Insights app\" }\r\n ,\"microsoft.insights/datacollectionendpoints\": { \"SingularDisplayName\": \"Data collection endpoint\" }\r\n ,\"microsoft.insights/datacollectionruleassociations\": { \"SingularDisplayName\": \"Microsoft.Insights data collection rule association\" }\r\n ,\"microsoft.insights/datacollectionrules\": { \"SingularDisplayName\": \"Data collection rule\" }\r\n ,\"microsoft.insights/datacollectionrulesresources\": { \"SingularDisplayName\": \"Data collection rule associated resource\" }\r\n ,\"microsoft.insights/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\r\n ,\"microsoft.insights/diagnosticsettingscategories\": { \"SingularDisplayName\": \"Microsoft.Insights diagnostic settings category\" }\r\n ,\"microsoft.insights/guestdiagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic setting\" }\r\n ,\"microsoft.insights/guestdiagnosticsettingsassociation\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic settings association\" }\r\n ,\"microsoft.insights/logprofiles\": { \"SingularDisplayName\": \"Microsoft.Insights logprofile\" }\r\n ,\"microsoft.insights/metricalerts\": { \"SingularDisplayName\": \"Metric alert rule\" }\r\n ,\"microsoft.insights/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights notification statu\" }\r\n ,\"microsoft.insights/privatelinkscopeoperationstatuses\": { \"SingularDisplayName\": \"Microsoft.insights private link scope operation statuse\" }\r\n ,\"microsoft.insights/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Monitor Private Link Scope\" }\r\n ,\"microsoft.insights/scheduledqueryrules\": { \"SingularDisplayName\": \"Log search alert rule\" }\r\n ,\"microsoft.insights/tenantactiongroups\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action group\" }\r\n ,\"microsoft.insights/tenantactiongroups/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action groups notification statu\" }\r\n ,\"microsoft.insights/vminsightsonboardingstatuses\": { \"SingularDisplayName\": \"Microsoft.Insights VM insights onboarding statuse\" }\r\n ,\"microsoft.insights/webtests\": { \"SingularDisplayName\": \"Application Insights availability test\" }\r\n ,\"microsoft.insights/workbooks\": { \"SingularDisplayName\": \"Azure Workbook\" }\r\n ,\"microsoft.insights/workbooktemplates\": { \"SingularDisplayName\": \"Azure Workbook Template\" }\r\n ,\"microsoft.integrationspaces/spaces\": { \"SingularDisplayName\": \"Integration Environment\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twin\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/assets\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins asset\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/executionplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins execution plan\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/testplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test plan\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/tests\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test\" }\r\n ,\"microsoft.inventory/subscriptioninternalproperties\": { \"SingularDisplayName\": \"Microsoft.Inventory subscription internal property\" }\r\n ,\"microsoft.iotcentral/iotapps\": { \"SingularDisplayName\": \"IoT Central Application\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces\": { \"SingularDisplayName\": \"Firmware analysis workspace\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmware\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares/summaries\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmwares summary\" }\r\n ,\"microsoft.iotoperations/instances\": { \"SingularDisplayName\": \"Azure IoT Operations\" }\r\n ,\"microsoft.iotoperations/instances/brokers\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances broker\" }\r\n ,\"microsoft.iotoperations/instances/brokers/authentications\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authentication\" }\r\n ,\"microsoft.iotoperations/instances/brokers/authorizations\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authorization\" }\r\n ,\"microsoft.iotoperations/instances/brokers/listeners\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers listener\" }\r\n ,\"microsoft.iotoperations/instances/dataflowendpoints\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow endpoint\" }\r\n ,\"microsoft.iotoperations/instances/dataflowprofiles\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profile\" }\r\n ,\"microsoft.iotoperations/instances/dataflowprofiles/dataflows\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profiles dataflow\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instance\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances dataset\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances pipeline\" }\r\n ,\"microsoft.iotoperationsmq/mq\": { \"SingularDisplayName\": \"IoT Operations Ops MQ\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/authentication\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authentication\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/authorization\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authorization\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/listener\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker listener\" }\r\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector topic map\" }\r\n ,\"microsoft.iotoperationsmq/mq/diagnosticservice\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq diagnostic service\" }\r\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector topic map\" }\r\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector topic map\" }\r\n ,\"microsoft.iotoperationsorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator instance\" }\r\n ,\"microsoft.iotoperationsorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator solution\" }\r\n ,\"microsoft.iotoperationsorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator target\" }\r\n ,\"microsoft.iotsecurity/alerttypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity alert type\" }\r\n ,\"microsoft.iotsecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity defender setting\" }\r\n ,\"microsoft.iotsecurity/onpremisesensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity on premise sensor\" }\r\n ,\"microsoft.iotsecurity/recommendationtypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity recommendation type\" }\r\n ,\"microsoft.iotsecurity/sensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity sensor\" }\r\n ,\"microsoft.iotsecurity/sites\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity site\" }\r\n ,\"microsoft.keyvault/managedhsms\": { \"SingularDisplayName\": \"Azure Key Vault Managed HSM\" }\r\n ,\"microsoft.keyvault/vaults\": { \"SingularDisplayName\": \"Key vault\" }\r\n ,\"microsoft.kubernetes/connectedclusters\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc extension\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc namespace\" }\r\n ,\"microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\r\n ,\"microsoft.kubernetesconfiguration/extensiontypes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension type\" }\r\n ,\"microsoft.kubernetesconfiguration/extensiontypes/versions\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension types version\" }\r\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configuration\" }\r\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations/operations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configurations operation\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scope\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private endpoint connection\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private link resource\" }\r\n ,\"microsoft.kubernetesconfiguration/sourcecontrolconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration source control configuration\" }\r\n ,\"microsoft.kubernetesruntime/bgppeers\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime bgp peer\" }\r\n ,\"microsoft.kubernetesruntime/loadbalancers\": { \"SingularDisplayName\": \"Arc Load Balancer\" }\r\n ,\"microsoft.kubernetesruntime/services\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime service\" }\r\n ,\"microsoft.kubernetesruntime/storageclasses\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime storage class\" }\r\n ,\"microsoft.kusto/clusters\": { \"SingularDisplayName\": \"Azure Data Explorer Cluster\" }\r\n ,\"microsoft.kusto/clusters/databases\": { \"SingularDisplayName\": \"Azure Data Explorer Database\" }\r\n ,\"microsoft.labservices/labaccounts\": { \"SingularDisplayName\": \"Lab account\" }\r\n ,\"microsoft.labservices/labaccounts/labs\": { \"SingularDisplayName\": \"Lab\" }\r\n ,\"microsoft.labservices/labplans\": { \"SingularDisplayName\": \"Lab plan\" }\r\n ,\"microsoft.labservices/labs\": { \"SingularDisplayName\": \"Lab\" }\r\n ,\"microsoft.liftrpilot/organizations\": { \"SingularDisplayName\": \"Azure Pilot\" }\r\n ,\"microsoft.loadtestservice/loadtestmappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test mapping\" }\r\n ,\"microsoft.loadtestservice/loadtestprofilemappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test profile mapping\" }\r\n ,\"microsoft.loadtestservice/loadtests\": { \"SingularDisplayName\": \"Azure Load Testing\" }\r\n ,\"microsoft.loadtestservice/playwrightworkspaces\": { \"SingularDisplayName\": \"Playwright Workspace\" }\r\n ,\"microsoft.logic/businessprocesses\": { \"SingularDisplayName\": \"Business Process\" }\r\n ,\"microsoft.logic/integrationaccounts\": { \"SingularDisplayName\": \"Logic app integration account\" }\r\n ,\"microsoft.logic/integrationserviceenvironments\": { \"SingularDisplayName\": \"Integration Service Environment\" }\r\n ,\"microsoft.logic/integrationserviceenvironments/health\": { \"SingularDisplayName\": \"Microsoft.Logic integration service environments health\" }\r\n ,\"microsoft.logic/integrationserviceenvironments/managedapis\": { \"SingularDisplayName\": \"Managed Connector\" }\r\n ,\"microsoft.logic/templates\": { \"SingularDisplayName\": \"Logic Apps Template\" }\r\n ,\"microsoft.logic/workflows\": { \"SingularDisplayName\": \"Logic app\" }\r\n ,\"microsoft.logz/monitors\": { \"SingularDisplayName\": \"Logz.io\" }\r\n ,\"microsoft.logz/monitors/accounts\": { \"SingularDisplayName\": \"Logz sub account\" }\r\n ,\"microsoft.m365/m365resources\": { \"SingularDisplayName\": \"Microsoft.M365 m365 resource\" }\r\n ,\"microsoft.m365consumptionservices/services\": { \"SingularDisplayName\": \"Microsoft.M365ConsumptionServices service\" }\r\n ,\"microsoft.machinelearning/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plan\" }\r\n ,\"microsoft.machinelearning/commitmentplans/commitmentassociations\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plans commitment association\" }\r\n ,\"microsoft.machinelearning/webservices\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) web service\" }\r\n ,\"microsoft.machinelearning/workspaces\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) workspace\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation account\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspace\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspaces project\" }\r\n ,\"microsoft.machinelearningservices/aistudio\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.machinelearningservices/aistudiocreate\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.machinelearningservices/registries\": { \"SingularDisplayName\": \"Azure Machine Learning registry\" }\r\n ,\"microsoft.machinelearningservices/workspaces\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\r\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints\": { \"SingularDisplayName\": \"Machine learning online endpoint\" }\r\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints/deployments\": { \"SingularDisplayName\": \"Machine learning online deployment\" }\r\n ,\"microsoft.machinelearningservices/workspacescreate\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\r\n ,\"microsoft.maintenance/configurationassignments\": { \"SingularDisplayName\": \"Microsoft.Maintenance configuration assignment\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurations\": { \"SingularDisplayName\": \"Maintenance Configuration\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurationsaumbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurationsbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\r\n ,\"microsoft.maintenance/publicmaintenanceconfigurations\": { \"SingularDisplayName\": \"Microsoft.Maintenance public maintenance configuration\" }\r\n ,\"microsoft.managedidentity/identities\": { \"SingularDisplayName\": \"Microsoft.ManagedIdentity identity\" }\r\n ,\"microsoft.managedidentity/userassignedidentities\": { \"SingularDisplayName\": \"Managed Identity\" }\r\n ,\"microsoft.managednetwork/managednetworks\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed network\" }\r\n ,\"microsoft.managednetwork/managednetworks/managednetworkgroups\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network group\" }\r\n ,\"microsoft.managednetwork/managednetworks/managednetworkpeeringpolicies\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network peering policy\" }\r\n ,\"microsoft.managednetworkfabric/accesscontrollists\": { \"SingularDisplayName\": \"Access Control List (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/internetgatewayrules\": { \"SingularDisplayName\": \"Internet Gateway Rule (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/internetgateways\": { \"SingularDisplayName\": \"Internet Gateway (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipcommunities\": { \"SingularDisplayName\": \"IP Community (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipextendedcommunities\": { \"SingularDisplayName\": \"IP Extended Community (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipprefixes\": { \"SingularDisplayName\": \"IP Prefix (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l2isolationdomains\": { \"SingularDisplayName\": \"Layer 2 Isolation Domain (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains\": { \"SingularDisplayName\": \"Layer 3 Isolation Domain (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains/externalnetworks\": { \"SingularDisplayName\": \"External Network (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains/internalnetworks\": { \"SingularDisplayName\": \"Internal Network (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/neighborgroups\": { \"SingularDisplayName\": \"Neighbor Group (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkdevices\": { \"SingularDisplayName\": \"Network Device (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkdevices/networkinterfaces\": { \"SingularDisplayName\": \"Network Interface (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabriccontrollers\": { \"SingularDisplayName\": \"Network Fabric Controller (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabrics\": { \"SingularDisplayName\": \"Network Fabric (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabrics/networktonetworkinterconnects\": { \"SingularDisplayName\": \"Network to Network Interconnect (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabricskus\": { \"SingularDisplayName\": \"Network Fabric SKU (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkmonitors\": { \"SingularDisplayName\": \"Microsoft.ManagedNetworkFabric network monitor\" }\r\n ,\"microsoft.managednetworkfabric/networkpacketbrokers\": { \"SingularDisplayName\": \"Network Packet Broker (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkracks\": { \"SingularDisplayName\": \"Network Rack (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networktaprules\": { \"SingularDisplayName\": \"Network Tap Rule (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networktaps\": { \"SingularDisplayName\": \"Network Tap (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/routepolicies\": { \"SingularDisplayName\": \"Route Policy (Operator Nexus)\" }\r\n ,\"microsoft.managedservices/marketplaceregistrationdefinitions\": { \"SingularDisplayName\": \"Microsoft.ManagedServices marketplace registration definition\" }\r\n ,\"microsoft.managedservices/registrationassignments\": { \"SingularDisplayName\": \"Microsoft.ManagedServices registration assignment\" }\r\n ,\"microsoft.managedservices/registrationdefinitions\": { \"SingularDisplayName\": \"Azure Lighthouse\" }\r\n ,\"microsoft.management/managementgroups\": { \"SingularDisplayName\": \"Microsoft.Management management group\" }\r\n ,\"microsoft.management/managementgroups/microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\r\n ,\"microsoft.management/managementgroups/providers/privatelinkassociations\": { \"SingularDisplayName\": \"Application Gateway\" }\r\n ,\"microsoft.management/managementgroups/providers/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\r\n ,\"microsoft.management/managementgroups/settings\": { \"SingularDisplayName\": \"Microsoft.Management management groups setting\" }\r\n ,\"microsoft.management/managementgroups/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Management management groups subscription\" }\r\n ,\"microsoft.management/servicegroups\": { \"SingularDisplayName\": \"Service group\" }\r\n ,\"microsoft.managementpartner/partners\": { \"SingularDisplayName\": \"Microsoft.ManagementPartner partner\" }\r\n ,\"microsoft.manufacturingplatform/manufacturingdataservices\": { \"SingularDisplayName\": \"Factory Operations Agent in Azure AI Foundry\" }\r\n ,\"microsoft.maps/accounts\": { \"SingularDisplayName\": \"Azure Maps Account\" }\r\n ,\"microsoft.maps/accounts/creators\": { \"SingularDisplayName\": \"Azure Maps Creator Resource\" }\r\n ,\"microsoft.marketplace/privatestores\": { \"SingularDisplayName\": \"Microsoft.Marketplace private store\" }\r\n ,\"microsoft.marketplace/privatestores/adminrequestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores admin request approval\" }\r\n ,\"microsoft.marketplace/privatestores/collections\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collection\" }\r\n ,\"microsoft.marketplace/privatestores/collections/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collections offer\" }\r\n ,\"microsoft.marketplace/privatestores/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores offer\" }\r\n ,\"microsoft.marketplace/privatestores/requestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores request approval\" }\r\n ,\"microsoft.media/mediaservices\": { \"SingularDisplayName\": \"Media service\" }\r\n ,\"microsoft.media/mediaservices/accountfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services account filter\" }\r\n ,\"microsoft.media/mediaservices/assets\": { \"SingularDisplayName\": \"Microsoft.Media media services asset\" }\r\n ,\"microsoft.media/mediaservices/assets/assetfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services assets asset filter\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks\": { \"SingularDisplayName\": \"Microsoft.Media media services assets track\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks/operationresults\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation result\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks/operationstatuses\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation statuse\" }\r\n ,\"microsoft.media/mediaservices/contentkeypolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services content key policy\" }\r\n ,\"microsoft.media/mediaservices/liveevents\": { \"SingularDisplayName\": \"Live event\" }\r\n ,\"microsoft.media/mediaservices/liveevents/liveoutputs\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices live events live output\" }\r\n ,\"microsoft.media/mediaservices/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private endpoint connection\" }\r\n ,\"microsoft.media/mediaservices/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private link resource\" }\r\n ,\"microsoft.media/mediaservices/streamingendpoints\": { \"SingularDisplayName\": \"Streaming Endpoint\" }\r\n ,\"microsoft.media/mediaservices/streaminglocators\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming locator\" }\r\n ,\"microsoft.media/mediaservices/streamingpolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming policy\" }\r\n ,\"microsoft.media/mediaservices/transforms\": { \"SingularDisplayName\": \"Microsoft.Media media services transform\" }\r\n ,\"microsoft.media/mediaservices/transforms/jobs\": { \"SingularDisplayName\": \"Microsoft.Media media services transforms job\" }\r\n ,\"microsoft.mesh/worlds\": { \"SingularDisplayName\": \"Microsoft.Mesh world\" }\r\n ,\"microsoft.mesh/worlds/events\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds event\" }\r\n ,\"microsoft.mesh/worlds/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds events access policy\" }\r\n ,\"microsoft.mesh/worlds/spaces\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds space\" }\r\n ,\"microsoft.mesh/worlds/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds spaces access policy\" }\r\n ,\"microsoft.mesh/worlds/templates\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds template\" }\r\n ,\"microsoft.mesh/worlds/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds templates access policy\" }\r\n ,\"microsoft.messagingcatalog/catalogs\": { \"SingularDisplayName\": \"Microsoft.MessagingCatalog catalog\" }\r\n ,\"microsoft.messagingconnectors/connectors\": { \"SingularDisplayName\": \"Microsoft.MessagingConnectors connector\" }\r\n ,\"microsoft.metaverse/metaverses\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverse\" }\r\n ,\"microsoft.metaverse/metaverses/events\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses event\" }\r\n ,\"microsoft.metaverse/metaverses/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses events access policy\" }\r\n ,\"microsoft.metaverse/metaverses/spaces\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses space\" }\r\n ,\"microsoft.metaverse/metaverses/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses spaces access policy\" }\r\n ,\"microsoft.metaverse/metaverses/templates\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses template\" }\r\n ,\"microsoft.metaverse/metaverses/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses templates access policy\" }\r\n ,\"microsoft.migrate/assessmentprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment project\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/clusters\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments cluster\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments avs assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business case\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/avssummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases avs summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedavsmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated avs machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedsqlentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated sql entity\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/iaassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases iaas summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/overviewsummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases overview summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/paassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases paas summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects group\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessments assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessments avs assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql database\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql instance\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/recommendedassessedentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments recommended assessed entity\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments web app service plan\" }\r\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/hypervcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects hypervcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/importcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects importcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/importsqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects import sql collector\" }\r\n ,\"microsoft.migrate/assessmentprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private endpoint connection\" }\r\n ,\"microsoft.migrate/assessmentprojects/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private link resource\" }\r\n ,\"microsoft.migrate/assessmentprojects/projectsummary\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects project summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/servercollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects servercollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql database\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql instance\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sqlcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/vmwarecollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects vmwarecollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments web app service plan\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app collector\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessments summary\" }\r\n ,\"microsoft.migrate/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate project\" }\r\n ,\"microsoft.migrate/migrateprojects/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database instance\" }\r\n ,\"microsoft.migrate/migrateprojects/databases\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database\" }\r\n ,\"microsoft.migrate/migrateprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects machine\" }\r\n ,\"microsoft.migrate/migrateprojects/migrateevents\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects migrate event\" }\r\n ,\"microsoft.migrate/migrateprojects/solutions\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects solution\" }\r\n ,\"microsoft.migrate/modernizeprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize project\" }\r\n ,\"microsoft.migrate/modernizeprojects/deployedresources\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects deployed resource\" }\r\n ,\"microsoft.migrate/modernizeprojects/jobs\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects job\" }\r\n ,\"microsoft.migrate/modernizeprojects/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects jobs operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/migrateagents\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agent\" }\r\n ,\"microsoft.migrate/modernizeprojects/migrateagents/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agents operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployment\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployments operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloadinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instance\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloadinstances/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instances operation\" }\r\n ,\"microsoft.migrate/movecollections\": { \"SingularDisplayName\": \"Microsoft.Migrate move collection\" }\r\n ,\"microsoft.migrate/movecollections/moveresources\": { \"SingularDisplayName\": \"Microsoft.Migrate move collections move resource\" }\r\n ,\"microsoft.migrate/projects\": { \"SingularDisplayName\": \"Migration project\" }\r\n ,\"microsoft.mission/approvals\": { \"SingularDisplayName\": \"Approval\" }\r\n ,\"microsoft.mission/catalogs\": { \"SingularDisplayName\": \"Catalog\" }\r\n ,\"microsoft.mission/communities\": { \"SingularDisplayName\": \"Community\" }\r\n ,\"microsoft.mission/communities/communityendpoints\": { \"SingularDisplayName\": \"Community endpoint\" }\r\n ,\"microsoft.mission/communities/transithubs\": { \"SingularDisplayName\": \"Transit hub\" }\r\n ,\"microsoft.mission/enclaveconnections\": { \"SingularDisplayName\": \"Enclave connection\" }\r\n ,\"microsoft.mission/externalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission external connection\" }\r\n ,\"microsoft.mission/internalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission internal connection\" }\r\n ,\"microsoft.mission/virtualenclaves\": { \"SingularDisplayName\": \"Enclave\" }\r\n ,\"microsoft.mission/virtualenclaves/enclaveendpoints\": { \"SingularDisplayName\": \"Enclave endpoint\" }\r\n ,\"microsoft.mission/virtualenclaves/endpoints\": { \"SingularDisplayName\": \"Endpoint\" }\r\n ,\"microsoft.mission/virtualenclaves/workloads\": { \"SingularDisplayName\": \"Workload\" }\r\n ,\"microsoft.mixedreality/objectanchorsaccounts\": { \"SingularDisplayName\": \"Object Anchors Account\" }\r\n ,\"microsoft.mixedreality/objectunderstandingaccounts\": { \"SingularDisplayName\": \"Object Understanding Account\" }\r\n ,\"microsoft.mixedreality/remoterenderingaccounts\": { \"SingularDisplayName\": \"Remote Rendering Account\" }\r\n ,\"microsoft.mixedreality/spatialanchorsaccounts\": { \"SingularDisplayName\": \"Spatial Anchors Account\" }\r\n ,\"microsoft.mixedreality/spatialmapsaccounts\": { \"SingularDisplayName\": \"Microsoft.MixedReality spatial maps account\" }\r\n ,\"microsoft.mobilenetwork/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork amf deployment\" }\r\n ,\"microsoft.mobilenetwork/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork cluster service\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks\": { \"SingularDisplayName\": \"Mobile Network\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/datanetworks\": { \"SingularDisplayName\": \"Data Network\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/services\": { \"SingularDisplayName\": \"Service\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/simpolicies\": { \"SingularDisplayName\": \"SIM Policy\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/sites\": { \"SingularDisplayName\": \"Mobile Network Site\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/slices\": { \"SingularDisplayName\": \"Slice\" }\r\n ,\"microsoft.mobilenetwork/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nrf deployment\" }\r\n ,\"microsoft.mobilenetwork/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nssf deployment\" }\r\n ,\"microsoft.mobilenetwork/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork observability service\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes\": { \"SingularDisplayName\": \"Packet Core Control Plane\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes\": { \"SingularDisplayName\": \"Packet Core Data Plane\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes/attacheddatanetworks\": { \"SingularDisplayName\": \"Attached Data Network\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplaneversions\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork packet core control plane version\" }\r\n ,\"microsoft.mobilenetwork/radioaccessnetworks\": { \"SingularDisplayName\": \"Radio Access Network Insights\" }\r\n ,\"microsoft.mobilenetwork/sdmdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sdm deployment\" }\r\n ,\"microsoft.mobilenetwork/simgroups\": { \"SingularDisplayName\": \"SIM Group\" }\r\n ,\"microsoft.mobilenetwork/simgroups/sims\": { \"SingularDisplayName\": \"SIM\" }\r\n ,\"microsoft.mobilenetwork/sims\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sim\" }\r\n ,\"microsoft.mobilenetwork/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork smf deployment\" }\r\n ,\"microsoft.mobilenetwork/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork upf deployment\" }\r\n ,\"microsoft.mobilenetwork/virtualizedmmedeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork virtualized mme deployment\" }\r\n ,\"microsoft.mobilenetwork/vnfagentdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork vnf agent deployment\" }\r\n ,\"microsoft.mobilepacketcore/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore amf deployment\" }\r\n ,\"microsoft.mobilepacketcore/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore cluster service\" }\r\n ,\"microsoft.mobilepacketcore/networkfunctions\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore network function\" }\r\n ,\"microsoft.mobilepacketcore/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nrf deployment\" }\r\n ,\"microsoft.mobilepacketcore/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nssf deployment\" }\r\n ,\"microsoft.mobilepacketcore/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore observability service\" }\r\n ,\"microsoft.mobilepacketcore/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore smf deployment\" }\r\n ,\"microsoft.mobilepacketcore/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore upf deployment\" }\r\n ,\"microsoft.modsimworkbench/workbenches\": { \"SingularDisplayName\": \"Modeling and Simulation Workbench\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers\": { \"SingularDisplayName\": \"Chamber\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/connectors\": { \"SingularDisplayName\": \"Chamber Connector\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/filerequests\": { \"SingularDisplayName\": \"Chamber Data Pipeline File Request\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/files\": { \"SingularDisplayName\": \"Chamber Data Pipeline File\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/licenses\": { \"SingularDisplayName\": \"Chamber License\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/storages\": { \"SingularDisplayName\": \"Chamber Storage\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/workloads\": { \"SingularDisplayName\": \"Chamber VM\" }\r\n ,\"microsoft.modsimworkbench/workbenches/sharedstorages\": { \"SingularDisplayName\": \"Shared Storage\" }\r\n ,\"microsoft.monitor/accounts\": { \"SingularDisplayName\": \"Azure Monitor workspace\" }\r\n ,\"microsoft.monitor/investigations\": { \"SingularDisplayName\": \"Microsoft.Monitor investigation\" }\r\n ,\"microsoft.monitor/pipelinegroups\": { \"SingularDisplayName\": \"Azure Monitor pipeline\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsite\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites agent\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites error summary\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/mysqlservers\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites my sqlserver\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/summaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites summary\" }\r\n ,\"microsoft.netapp/netappaccounts\": { \"SingularDisplayName\": \"NetApp account\" }\r\n ,\"microsoft.netapp/netappaccounts/backuppolicies\": { \"SingularDisplayName\": \"Backup Policy\" }\r\n ,\"microsoft.netapp/netappaccounts/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools\": { \"SingularDisplayName\": \"Capacity pool\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes\": { \"SingularDisplayName\": \"Volume\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/volumequotarules\": { \"SingularDisplayName\": \"User and group quota\" }\r\n ,\"microsoft.netapp/netappaccounts/snapshotpolicies\": { \"SingularDisplayName\": \"Snapshot policy\" }\r\n ,\"microsoft.netapp/netappaccounts/volumegroups\": { \"SingularDisplayName\": \"VolumeGroup\" }\r\n ,\"microsoft.network/applicationgatewayavailablessloptions\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl option\" }\r\n ,\"microsoft.network/applicationgatewayavailablessloptions/predefinedpolicies\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl options predefined policy\" }\r\n ,\"microsoft.network/applicationgateways\": { \"SingularDisplayName\": \"Application gateway\" }\r\n ,\"microsoft.network/applicationgatewaywebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Application Gateway WAF policy\" }\r\n ,\"microsoft.network/applicationsecuritygroups\": { \"SingularDisplayName\": \"Application security group\" }\r\n ,\"microsoft.network/azurefirewalls\": { \"SingularDisplayName\": \"Firewall\" }\r\n ,\"microsoft.network/azurewebcategories\": { \"SingularDisplayName\": \"Microsoft.Network Azure web category\" }\r\n ,\"microsoft.network/bastionhosts\": { \"SingularDisplayName\": \"Bastion\" }\r\n ,\"microsoft.network/cloudserviceslots\": { \"SingularDisplayName\": \"Microsoft.Network cloud service slot\" }\r\n ,\"microsoft.network/connections\": { \"SingularDisplayName\": \"Connection\" }\r\n ,\"microsoft.network/customipprefixes\": { \"SingularDisplayName\": \"Custom IP Prefix\" }\r\n ,\"microsoft.network/ddoscustompolicies\": { \"SingularDisplayName\": \"Microsoft.Network DDoS custom policy\" }\r\n ,\"microsoft.network/ddosprotectionplans\": { \"SingularDisplayName\": \"DDoS protection plan\" }\r\n ,\"microsoft.network/dnsforwardingrulesets\": { \"SingularDisplayName\": \"DNS forwarding ruleset\" }\r\n ,\"microsoft.network/dnsresolverdomainlists\": { \"SingularDisplayName\": \"DNS Domain List\" }\r\n ,\"microsoft.network/dnsresolverpolicies\": { \"SingularDisplayName\": \"DNS Security Policy\" }\r\n ,\"microsoft.network/dnsresolvers\": { \"SingularDisplayName\": \"DNS private resolver\" }\r\n ,\"microsoft.network/dnszones\": { \"SingularDisplayName\": \"DNS zone\" }\r\n ,\"microsoft.network/dscpconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network DSCP configuration\" }\r\n ,\"microsoft.network/expressroutecircuits\": { \"SingularDisplayName\": \"ExpressRoute circuit\" }\r\n ,\"microsoft.network/expressroutecrossconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connection\" }\r\n ,\"microsoft.network/expressroutecrossconnections/peerings\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connections peering\" }\r\n ,\"microsoft.network/expressroutegateways\": { \"SingularDisplayName\": \"ExpressRoute Gateway\" }\r\n ,\"microsoft.network/expressroutegateways/expressrouteconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route gateways express route connection\" }\r\n ,\"microsoft.network/expressrouteports\": { \"SingularDisplayName\": \"ExpressRoute Direct\" }\r\n ,\"microsoft.network/expressrouteportslocations\": { \"SingularDisplayName\": \"Microsoft.Network express route ports location\" }\r\n ,\"microsoft.network/firewallpolicies\": { \"SingularDisplayName\": \"Firewall Policy\" }\r\n ,\"microsoft.network/frontdoors\": { \"SingularDisplayName\": \"Front Door and CDN profiles\" }\r\n ,\"microsoft.network/frontdoorwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Front Door WAF policy\" }\r\n ,\"microsoft.network/ipallocations\": { \"SingularDisplayName\": \"Microsoft.Network IP allocation\" }\r\n ,\"microsoft.network/ipgroups\": { \"SingularDisplayName\": \"IP Group\" }\r\n ,\"microsoft.network/loadbalancers\": { \"SingularDisplayName\": \"Load balancer\" }\r\n ,\"microsoft.network/localnetworkgateways\": { \"SingularDisplayName\": \"Local network gateway\" }\r\n ,\"microsoft.network/natgateways\": { \"SingularDisplayName\": \"NAT gateway\" }\r\n ,\"microsoft.network/networkexperimentprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profile\" }\r\n ,\"microsoft.network/networkexperimentprofiles/experiments\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profiles experiment\" }\r\n ,\"microsoft.network/networkinterfaces\": { \"SingularDisplayName\": \"Network interface\" }\r\n ,\"microsoft.network/networkmanagerconnections\": { \"SingularDisplayName\": \"Microsoft.Network network manager connection\" }\r\n ,\"microsoft.network/networkmanagers\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/connectivityconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/ipampools\": { \"SingularDisplayName\": \"IP address pool\" }\r\n ,\"microsoft.network/networkmanagers/networkgroups\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/routingconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/securityadminconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/securityuserconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/verifierworkspaces\": { \"SingularDisplayName\": \"Verifier Workspace\" }\r\n ,\"microsoft.network/networkprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network profile\" }\r\n ,\"microsoft.network/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group\" }\r\n ,\"microsoft.network/networksecurityperimeters\": { \"SingularDisplayName\": \"Network Security Perimeter\" }\r\n ,\"microsoft.network/networksecurityperimeters/profiles\": { \"SingularDisplayName\": \"Network Security Perimeter Profile\" }\r\n ,\"microsoft.network/networkverifiers\": { \"SingularDisplayName\": \"Virtual Network Verifier\" }\r\n ,\"microsoft.network/networkvirtualappliances\": { \"SingularDisplayName\": \"Microsoft.Network network virtual appliance\" }\r\n ,\"microsoft.network/networkwatchers\": { \"SingularDisplayName\": \"Network Watcher\" }\r\n ,\"microsoft.network/networkwatchers/flowlogs\": { \"SingularDisplayName\": \"Flow log\" }\r\n ,\"microsoft.network/p2svpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Point to Site)\" }\r\n ,\"microsoft.network/privatednszones\": { \"SingularDisplayName\": \"Private DNS zone\" }\r\n ,\"microsoft.network/privatednszones/virtualnetworklinks\": { \"SingularDisplayName\": \"Virtual network link\" }\r\n ,\"microsoft.network/privateendpoints\": { \"SingularDisplayName\": \"Private endpoint\" }\r\n ,\"microsoft.network/privatelinkservices\": { \"SingularDisplayName\": \"Private link service\" }\r\n ,\"microsoft.network/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\r\n ,\"microsoft.network/publicipprefixes\": { \"SingularDisplayName\": \"Public IP Prefix\" }\r\n ,\"microsoft.network/routefilters\": { \"SingularDisplayName\": \"Route filter\" }\r\n ,\"microsoft.network/routetables\": { \"SingularDisplayName\": \"Route table\" }\r\n ,\"microsoft.network/securitypartnerproviders\": { \"SingularDisplayName\": \"Microsoft.Network security partner provider\" }\r\n ,\"microsoft.network/serviceendpointpolicies\": { \"SingularDisplayName\": \"Service endpoint policy\" }\r\n ,\"microsoft.network/trafficmanagergeographichierarchies\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager geographic hierarchy\" }\r\n ,\"microsoft.network/trafficmanagerprofiles\": { \"SingularDisplayName\": \"Traffic Manager profile\" }\r\n ,\"microsoft.network/trafficmanagerusermetricskeys\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager user metrics key\" }\r\n ,\"microsoft.network/virtualhubs\": { \"SingularDisplayName\": \"Microsoft.Network/virtualHub\" }\r\n ,\"microsoft.network/virtualnetworkgateways\": { \"SingularDisplayName\": \"Virtual network gateway\" }\r\n ,\"microsoft.network/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network\" }\r\n ,\"microsoft.network/virtualnetworktaps\": { \"SingularDisplayName\": \"Virtual network terminal access point\" }\r\n ,\"microsoft.network/virtualrouters\": { \"SingularDisplayName\": \"Microsoft.Network virtual router\" }\r\n ,\"microsoft.network/virtualrouters/peerings\": { \"SingularDisplayName\": \"Microsoft.Network virtual routers peering\" }\r\n ,\"microsoft.network/virtualwans\": { \"SingularDisplayName\": \"Virtual WAN\" }\r\n ,\"microsoft.network/vpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Site to Site)\" }\r\n ,\"microsoft.network/vpngateways/vpnconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connection\" }\r\n ,\"microsoft.network/vpngateways/vpnconnections/vpnlinkconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connections VPN link connection\" }\r\n ,\"microsoft.network/vpnserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network VPN server configuration\" }\r\n ,\"microsoft.network/vpnsites\": { \"SingularDisplayName\": \"Microsoft.Network VPN site\" }\r\n ,\"microsoft.network/vpnsites/vpnsitelinks\": { \"SingularDisplayName\": \"Microsoft.Network VPN sites VPN site link\" }\r\n ,\"microsoft.networkanalytics/dataconnectors\": { \"SingularDisplayName\": \"AIOps - Data Connector\" }\r\n ,\"microsoft.networkanalytics/datalakehouses\": { \"SingularDisplayName\": \"AIOps - Data LakeHouse\" }\r\n ,\"microsoft.networkanalytics/dataproducts\": { \"SingularDisplayName\": \"Azure Operator Insights ? Data Product\" }\r\n ,\"microsoft.networkanalytics/dataproducts/datatypes\": { \"SingularDisplayName\": \"Data Type\" }\r\n ,\"microsoft.networkanalytics/dataproductscatalogs\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics data products catalog\" }\r\n ,\"microsoft.networkanalytics/metricsingestionendpoints\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics metrics ingestion endpoint\" }\r\n ,\"microsoft.networkanalytics/networkanalyticsproducts\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics network analytics product\" }\r\n ,\"microsoft.networkcloud/baremetalmachines\": { \"SingularDisplayName\": \"Bare Metal Machine (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/cloudservicesnetworks\": { \"SingularDisplayName\": \"Cloud Services Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clustermanagers\": { \"SingularDisplayName\": \"Cluster Manager (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters\": { \"SingularDisplayName\": \"Cluster (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/baremetalmachinekeysets\": { \"SingularDisplayName\": \"Cluster Bare Metal Machine Key Set (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/bmckeysets\": { \"SingularDisplayName\": \"Cluster Baseboard Management Controller Key Set (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/metricsconfigurations\": { \"SingularDisplayName\": \"Cluster Metrics Configuration (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/edgeclustermachineskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster machine SKU\" }\r\n ,\"microsoft.networkcloud/edgeclusterruntimeversions\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster runtime version\" }\r\n ,\"microsoft.networkcloud/edgeclusters\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster\" }\r\n ,\"microsoft.networkcloud/edgeclusters/nodes\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge clusters node\" }\r\n ,\"microsoft.networkcloud/edgeclusterskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster SKU\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters\": { \"SingularDisplayName\": \"Kubernetes Cluster (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters/agentpools\": { \"SingularDisplayName\": \"Agent Pool (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters/features\": { \"SingularDisplayName\": \"Kubernetes Cluster Feature (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/l2networks\": { \"SingularDisplayName\": \"Layer 2 Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/l3networks\": { \"SingularDisplayName\": \"Layer 3 Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/racks\": { \"SingularDisplayName\": \"Compute Rack (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/rackskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud rack SKU\" }\r\n ,\"microsoft.networkcloud/registrationhubs\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hub\" }\r\n ,\"microsoft.networkcloud/registrationhubs/images\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs image\" }\r\n ,\"microsoft.networkcloud/registrationhubs/machines\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs machine\" }\r\n ,\"microsoft.networkcloud/storageappliances\": { \"SingularDisplayName\": \"Storage Appliance (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/trunkednetworks\": { \"SingularDisplayName\": \"Trunked Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/virtualmachines\": { \"SingularDisplayName\": \"Virtual Machine (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/virtualmachines/consoles\": { \"SingularDisplayName\": \"Virtual Machine Console (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/volumes\": { \"SingularDisplayName\": \"Volume (Operator Nexus)\" }\r\n ,\"microsoft.networkfunction/azuretrafficcollectors\": { \"SingularDisplayName\": \"ExpressRoute traffic collector\" }\r\n ,\"microsoft.networkfunction/meshvpns\": { \"SingularDisplayName\": \"Mesh VPN\" }\r\n ,\"microsoft.nexusidentity/identitycontrollers\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity controller\" }\r\n ,\"microsoft.nexusidentity/identitysets\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity set\" }\r\n ,\"microsoft.notebooks/notebookproxies\": { \"SingularDisplayName\": \"Microsoft.Notebooks notebook proxy\" }\r\n ,\"microsoft.notificationhubs/namespaces\": { \"SingularDisplayName\": \"Notification Hub Namespace\" }\r\n ,\"microsoft.notificationhubs/namespaces/notificationhubs\": { \"SingularDisplayName\": \"Notification Hub\" }\r\n ,\"microsoft.objectstore/osnamespaces\": { \"SingularDisplayName\": \"Microsoft.ObjectStore os namespace\" }\r\n })[tolower(id)]\r\n}\r\n", + "$fxv#3": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_4(id: string) {\r\n dynamic({\r\n \"microsoft.offazure/hypervsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv site\" }\r\n ,\"microsoft.offazure/hypervsites/clusters\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites cluster\" }\r\n ,\"microsoft.offazure/hypervsites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites host\" }\r\n ,\"microsoft.offazure/hypervsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites job\" }\r\n ,\"microsoft.offazure/hypervsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machine\" }\r\n ,\"microsoft.offazure/hypervsites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machines software inventory\" }\r\n ,\"microsoft.offazure/hypervsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites operations statu\" }\r\n ,\"microsoft.offazure/hypervsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites run as account\" }\r\n ,\"microsoft.offazure/importsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure import site\" }\r\n ,\"microsoft.offazure/importsites/deletejobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites delete job\" }\r\n ,\"microsoft.offazure/importsites/exportjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites export job\" }\r\n ,\"microsoft.offazure/importsites/importjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites import job\" }\r\n ,\"microsoft.offazure/importsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites job\" }\r\n ,\"microsoft.offazure/importsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites machine\" }\r\n ,\"microsoft.offazure/mastersites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master site\" }\r\n ,\"microsoft.offazure/mastersites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites operations statu\" }\r\n ,\"microsoft.offazure/mastersites/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private endpoint connection\" }\r\n ,\"microsoft.offazure/mastersites/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private link resource\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql site\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites discovery site data source\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites job\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites operations statu\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites run as account\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqlavailabilitygroups\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql availability group\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqldatabases\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql database\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqlservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql server\" }\r\n ,\"microsoft.offazure/mastersites/webappsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app site\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites discovery site data source\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/extendedmachines\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites extended machine\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/iiswebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web application\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/iiswebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web server\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites runasaccount\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web application\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web server\" }\r\n ,\"microsoft.offazure/serversites\": { \"SingularDisplayName\": \"Microsoft.OffAzure server site\" }\r\n ,\"microsoft.offazure/serversites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites job\" }\r\n ,\"microsoft.offazure/serversites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machine\" }\r\n ,\"microsoft.offazure/serversites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machines software inventory\" }\r\n ,\"microsoft.offazure/serversites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites operations statu\" }\r\n ,\"microsoft.offazure/serversites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites run as account\" }\r\n ,\"microsoft.offazure/vmwaresites\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware site\" }\r\n ,\"microsoft.offazure/vmwaresites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites host\" }\r\n ,\"microsoft.offazure/vmwaresites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites job\" }\r\n ,\"microsoft.offazure/vmwaresites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machine\" }\r\n ,\"microsoft.offazure/vmwaresites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machines software inventory\" }\r\n ,\"microsoft.offazure/vmwaresites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites operations statu\" }\r\n ,\"microsoft.offazure/vmwaresites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites run as account\" }\r\n ,\"microsoft.offazure/vmwaresites/vcenters\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites vcenter\" }\r\n ,\"microsoft.offazurespringboot/springbootsites\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsite\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites error summary\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/springbootapps\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootapp\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/springbootservers\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootserver\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/summaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites summary\" }\r\n ,\"microsoft.onlineexperimentation/workspaces\": { \"SingularDisplayName\": \"Online Experimentation Workspace\" }\r\n ,\"microsoft.openenergyplatform/energyservices\": { \"SingularDisplayName\": \"Azure Data Manager for Energy\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspace\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/applicationregistrations\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application registration\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/applications\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/eventgridfilters\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces event grid filter\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/shares\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/sharesubscriptions\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share subscription\" }\r\n ,\"microsoft.operationalinsights/clusters\": { \"SingularDisplayName\": \"Log Analytics dedicated cluster\" }\r\n ,\"microsoft.operationalinsights/querypacks\": { \"SingularDisplayName\": \"Log Analytics query pack\" }\r\n ,\"microsoft.operationalinsights/workspaces\": { \"SingularDisplayName\": \"Log Analytics workspace\" }\r\n ,\"microsoft.operationsmanagement/managementassociations\": { \"SingularDisplayName\": \"Microsoft.OperationsManagement management association\" }\r\n ,\"microsoft.operationsmanagement/solutions\": { \"SingularDisplayName\": \"Solution\" }\r\n ,\"microsoft.operatorvoicemail/operatorvoicemailinstances\": { \"SingularDisplayName\": \"Microsoft.OperatorVoicemail operator voicemail instance\" }\r\n ,\"microsoft.oraclediscovery/oraclesites\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle site\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites error summary\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/oracledatabases\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle database\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/oracleservers\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle server\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/summaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites summary\" }\r\n ,\"microsoft.orbital/cloudaccessrouters\": { \"SingularDisplayName\": \"Cloud Access Router\" }\r\n ,\"microsoft.orbital/contactprofiles\": { \"SingularDisplayName\": \"Contact Profile\" }\r\n ,\"microsoft.orbital/edgesites\": { \"SingularDisplayName\": \"Edge Site\" }\r\n ,\"microsoft.orbital/geocatalogs\": { \"SingularDisplayName\": \"GeoCatalog\" }\r\n ,\"microsoft.orbital/globalcommunicationssites\": { \"SingularDisplayName\": \"Microsoft.Orbital global communications site\" }\r\n ,\"microsoft.orbital/groundstations\": { \"SingularDisplayName\": \"Ground Station\" }\r\n ,\"microsoft.orbital/l2connections\": { \"SingularDisplayName\": \"L2 Connection\" }\r\n ,\"microsoft.orbital/sdwancontrollers\": { \"SingularDisplayName\": \"SDWAN Controller\" }\r\n ,\"microsoft.orbital/spacecrafts\": { \"SingularDisplayName\": \"Spacecraft\" }\r\n ,\"microsoft.orbital/spacecrafts/contacts\": { \"SingularDisplayName\": \"Contact\" }\r\n ,\"microsoft.orbital/terminals\": { \"SingularDisplayName\": \"Cloud Access Terminal\" }\r\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrence\" }\r\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences/operationresult\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrences operation result\" }\r\n ,\"microsoft.peering/peerasns\": { \"SingularDisplayName\": \"Microsoft.Peering peer asn\" }\r\n ,\"microsoft.peering/peerings\": { \"SingularDisplayName\": \"Peering\" }\r\n ,\"microsoft.peering/peerings/registeredasns\": { \"SingularDisplayName\": \"Registered ASN\" }\r\n ,\"microsoft.peering/peerings/registeredprefixes\": { \"SingularDisplayName\": \"Registered prefix\" }\r\n ,\"microsoft.peering/peeringservices\": { \"SingularDisplayName\": \"Peering Service\" }\r\n ,\"microsoft.peering/peeringservices/prefixes\": { \"SingularDisplayName\": \"Peering Service Prefix\" }\r\n ,\"microsoft.pki/pkis\": { \"SingularDisplayName\": \"Microsoft.Pki PKI\" }\r\n ,\"microsoft.pki/pkis/certificateauthorities\": { \"SingularDisplayName\": \"Microsoft.Pki pkis certificate authority\" }\r\n ,\"microsoft.pki/pkis/enrollmentpolicies\": { \"SingularDisplayName\": \"Microsoft.Pki pkis enrollment policy\" }\r\n ,\"microsoft.policyinsights/attestations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights attestation\" }\r\n ,\"microsoft.policyinsights/policymetadata\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights policy metadata\" }\r\n ,\"microsoft.policyinsights/remediations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights remediation\" }\r\n ,\"microsoft.portal/consoles\": { \"SingularDisplayName\": \"Microsoft.Portal console\" }\r\n ,\"microsoft.portal/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\r\n ,\"microsoft.portal/tenantconfigurations\": { \"SingularDisplayName\": \"Microsoft.Portal tenant configuration\" }\r\n ,\"microsoft.portal/usersettings\": { \"SingularDisplayName\": \"Microsoft.Portal user setting\" }\r\n ,\"microsoft.portal/virtual-privatedashboards\": { \"SingularDisplayName\": \"Private dashboard\" }\r\n ,\"microsoft.portalservices/copilotsettings\": { \"SingularDisplayName\": \"Microsoft.PortalServices copilot setting\" }\r\n ,\"microsoft.portalservices/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\r\n ,\"microsoft.portalservices/extensions\": { \"SingularDisplayName\": \"Portal Extension\" }\r\n ,\"microsoft.portalservices/extensions/deployments\": { \"SingularDisplayName\": \"Extension Deployment\" }\r\n ,\"microsoft.portalservices/extensions/slots\": { \"SingularDisplayName\": \"Extension Slot\" }\r\n ,\"microsoft.portalservices/extensions/versions\": { \"SingularDisplayName\": \"Extension Version\" }\r\n ,\"microsoft.portalservices/settings\": { \"SingularDisplayName\": \"Microsoft.PortalServices setting\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private endpoint connection\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private link resource\" }\r\n ,\"microsoft.powerbi/workspacecollections\": { \"SingularDisplayName\": \"Microsoft.PowerBI workspace collection\" }\r\n ,\"microsoft.powerbidedicated/autoscalevcores\": { \"SingularDisplayName\": \"Microsoft.PowerBIDedicated auto scale vcore\" }\r\n ,\"microsoft.powerbidedicated/capacities\": { \"SingularDisplayName\": \"Power BI Embedded\" }\r\n ,\"microsoft.powerplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.PowerPlatform account\" }\r\n ,\"microsoft.premonition/libraries\": { \"SingularDisplayName\": \"Microsoft.Premonition library\" }\r\n ,\"microsoft.premonition/libraries/analyses\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries analyse\" }\r\n ,\"microsoft.premonition/libraries/samples\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries sample\" }\r\n ,\"microsoft.professionalservice/resources\": { \"SingularDisplayName\": \"Professional Service\" }\r\n ,\"microsoft.programmableconnectivity/gateways\": { \"SingularDisplayName\": \"APC Gateway\" }\r\n ,\"microsoft.programmableconnectivity/operatorapiconnections\": { \"SingularDisplayName\": \"APC Operator API Connection\" }\r\n ,\"microsoft.programmableconnectivity/operatorapiplans\": { \"SingularDisplayName\": \"APC Operator API Plan\" }\r\n ,\"microsoft.proposal/proposals\": { \"SingularDisplayName\": \"Microsoft.Proposal proposal\" }\r\n ,\"microsoft.providerhub/providerregistrations\": { \"SingularDisplayName\": \"Resource Provider as a Service\" }\r\n ,\"microsoft.providerhub/providerregistrations/customrollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.providerhub/providerregistrations/defaultrollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\r\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\r\n ,\"microsoft.providerhubdevtest/regionalstresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest regional stresstest\" }\r\n ,\"microsoft.providerhubdevtest/stresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest stresstest\" }\r\n ,\"microsoft.purview/accounts\": { \"SingularDisplayName\": \"Microsoft Purview account\" }\r\n ,\"microsoft.quantum/provideraccounts\": { \"SingularDisplayName\": \"Microsoft.Quantum provider account\" }\r\n ,\"microsoft.quantum/workspaces\": { \"SingularDisplayName\": \"Quantum Workspace\" }\r\n ,\"microsoft.quota/groupquotas\": { \"SingularDisplayName\": \"Microsoft.Quota group quota\" }\r\n ,\"microsoft.quota/groupquotas/groupquotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas group quota request\" }\r\n ,\"microsoft.quota/groupquotas/quotaallocationrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation request\" }\r\n ,\"microsoft.quota/groupquotas/quotaallocations\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation\" }\r\n ,\"microsoft.quota/groupquotas/subscriptionrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription request\" }\r\n ,\"microsoft.quota/groupquotas/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription\" }\r\n ,\"microsoft.quota/quotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota quota request\" }\r\n ,\"microsoft.quota/quotas\": { \"SingularDisplayName\": \"Microsoft.Quota quota\" }\r\n ,\"microsoft.quota/usages\": { \"SingularDisplayName\": \"Microsoft.Quota usage\" }\r\n ,\"microsoft.recommendationsservice/accounts\": { \"SingularDisplayName\": \"Intelligent Recommendations Account\" }\r\n ,\"microsoft.recommendationsservice/accounts/modeling\": { \"SingularDisplayName\": \"Modeling\" }\r\n ,\"microsoft.recommendationsservice/accounts/serviceendpoints\": { \"SingularDisplayName\": \"Service Endpoint\" }\r\n ,\"microsoft.recoveryservices/replicationeligibilityresults\": { \"SingularDisplayName\": \"Microsoft.RecoveryServices replication eligibility result\" }\r\n ,\"microsoft.recoveryservices/vaults\": { \"SingularDisplayName\": \"Recovery Services vault\" }\r\n ,\"microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems\": { \"SingularDisplayName\": \"Backup Item\" }\r\n ,\"microsoft.recoveryservicesbvtd/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD\" }\r\n ,\"microsoft.recoveryservicesbvtd2/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD2\" }\r\n ,\"microsoft.recoveryservicesintd/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD\" }\r\n ,\"microsoft.recoveryservicesintd2/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD2\" }\r\n ,\"microsoft.redhatopenshift/openshiftclusters\": { \"SingularDisplayName\": \"Azure Red Hat OpenShift cluster\" }\r\n ,\"microsoft.relationships/dependencyof\": { \"SingularDisplayName\": \"Dependency Relationship\" }\r\n ,\"microsoft.relationships/servicegroupmember\": { \"SingularDisplayName\": \"Service group member relationship\" }\r\n ,\"microsoft.relationships/servicegrouprelationships\": { \"SingularDisplayName\": \"Connected Resource\" }\r\n ,\"microsoft.relay/namespaces\": { \"SingularDisplayName\": \"Relay\" }\r\n ,\"microsoft.relay/namespaces/hybridconnections\": { \"SingularDisplayName\": \"Hybrid connection\" }\r\n ,\"microsoft.relay/namespaces/wcfrelays\": { \"SingularDisplayName\": \"WCF relay\" }\r\n ,\"microsoft.resilience/resiliencestates\": { \"SingularDisplayName\": \"Microsoft.Resilience resilience state\" }\r\n ,\"microsoft.resourceconnector/appliances\": { \"SingularDisplayName\": \"Resource bridge\" }\r\n ,\"microsoft.resourcegraph/queries\": { \"SingularDisplayName\": \"Resource Graph query\" }\r\n ,\"microsoft.resourcehealth/availabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth availability statuse\" }\r\n ,\"microsoft.resourcehealth/childavailabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth child availability statuse\" }\r\n ,\"microsoft.resourcehealth/emergingissues\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth emerging issue\" }\r\n ,\"microsoft.resourcehealth/events\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth event\" }\r\n ,\"microsoft.resourcehealth/events/impactedresources\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth events impacted resource\" }\r\n ,\"microsoft.resourcehealth/metadata\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth metadata\" }\r\n ,\"microsoft.resources/builtintemplatespecs\": { \"SingularDisplayName\": \"Built-in template spec\" }\r\n ,\"microsoft.resources/changes\": { \"SingularDisplayName\": \"Microsoft.Resources change\" }\r\n ,\"microsoft.resources/databoundaries\": { \"SingularDisplayName\": \"Microsoft.Resources data boundary\" }\r\n ,\"microsoft.resources/deletedresources\": { \"SingularDisplayName\": \"Recycle Bin\" }\r\n ,\"microsoft.resources/deployments\": { \"SingularDisplayName\": \"Microsoft.Resources deployment\" }\r\n ,\"microsoft.resources/deployments/operations\": { \"SingularDisplayName\": \"Microsoft.Resources deployments operation\" }\r\n ,\"microsoft.resources/deploymentscripts\": { \"SingularDisplayName\": \"Deployment Script\" }\r\n ,\"microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\r\n ,\"microsoft.resources/mobobrokers\": { \"SingularDisplayName\": \"Microsoft.Resources mobo broker\" }\r\n ,\"microsoft.resources/resourcechange\": { \"SingularDisplayName\": \"Change Analysis\" }\r\n ,\"microsoft.resources/resourcechanges\": { \"SingularDisplayName\": \"Resource change\" }\r\n ,\"microsoft.resources/resourcegraphvisualizer\": { \"SingularDisplayName\": \"Resource Graph Visualizer\" }\r\n ,\"microsoft.resources/resourcegroups\": { \"SingularDisplayName\": \"Microsoft.Resources resource group\" }\r\n ,\"microsoft.resources/resources\": { \"SingularDisplayName\": \"Resource\" }\r\n ,\"microsoft.resources/snapshots\": { \"SingularDisplayName\": \"Microsoft.Resources snapshot\" }\r\n ,\"microsoft.resources/subscriptions\": { \"SingularDisplayName\": \"Subscription\" }\r\n ,\"microsoft.resources/subscriptions/resourcegroups\": { \"SingularDisplayName\": \"Resource group\" }\r\n ,\"microsoft.resources/tags\": { \"SingularDisplayName\": \"Microsoft.Resources tag\" }\r\n ,\"microsoft.resources/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\r\n ,\"microsoft.resources/virtualsubscriptionsforresourcepicker\": { \"SingularDisplayName\": \"Subscription\" }\r\n ,\"microsoft.saas/applications\": { \"SingularDisplayName\": \"Software as a Service (classic)\" }\r\n ,\"microsoft.saas/resources\": { \"SingularDisplayName\": \"SaaS\" }\r\n ,\"microsoft.saas/saasresources\": { \"SingularDisplayName\": \"SaaS (classic)\" }\r\n ,\"microsoft.saashub/cloudservices\": { \"SingularDisplayName\": \"Microsoft.SaaSHub cloud service\" }\r\n ,\"microsoft.saashub/cloudservices/hidden\": { \"SingularDisplayName\": \"Microsoft SaaS\" }\r\n ,\"microsoft.saashub/saasresources\": { \"SingularDisplayName\": \"Microsoft.SaaSHub saas resource\" }\r\n ,\"microsoft.salescopilot/conversationintelligencerecordingaccounts\": { \"SingularDisplayName\": \"Microsoft.SalesCopilot conversation intelligence recording account\" }\r\n ,\"microsoft.scheduler/jobcollections\": { \"SingularDisplayName\": \"Scheduler job collection\" }\r\n ,\"microsoft.scheduler/jobcollections/jobs\": { \"SingularDisplayName\": \"Scheduler job\" }\r\n ,\"microsoft.scom/managedinstances\": { \"SingularDisplayName\": \"SCOM managed instance\" }\r\n ,\"microsoft.scvmm/availabilitysets\": { \"SingularDisplayName\": \"Microsoft.ScVmm availability set\" }\r\n ,\"microsoft.scvmm/clouds\": { \"SingularDisplayName\": \"Microsoft.ScVmm cloud\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instance\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances guest agent\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.scvmm/virtualmachines\": { \"SingularDisplayName\": \"SCVMM virtual machine - Azure Arc\" }\r\n ,\"microsoft.scvmm/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine template\" }\r\n ,\"microsoft.scvmm/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual network\" }\r\n ,\"microsoft.scvmm/vmmservers\": { \"SingularDisplayName\": \"SCVMM management server\" }\r\n ,\"microsoft.search/searchservices\": { \"SingularDisplayName\": \"Search service\" }\r\n ,\"microsoft.secretmanagementsampleprovider/forecasts\": { \"SingularDisplayName\": \"Microsoft.SecretManagementSampleProvider forecast\" }\r\n ,\"microsoft.secretsynccontroller/azurekeyvaultsecretproviderclasses\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController Azure key vault secret provider class\" }\r\n ,\"microsoft.secretsynccontroller/secretsyncs\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController secret sync\" }\r\n ,\"microsoft.security/adaptivenetworkhardenings\": { \"SingularDisplayName\": \"Microsoft.Security adaptive network hardening\" }\r\n ,\"microsoft.security/advancedthreatprotectionsettings\": { \"SingularDisplayName\": \"Microsoft.Security advanced threat protection setting\" }\r\n ,\"microsoft.security/alertssuppressionrules\": { \"SingularDisplayName\": \"Microsoft.Security alerts suppression rule\" }\r\n ,\"microsoft.security/apicollections\": { \"SingularDisplayName\": \"Microsoft.Security API collection\" }\r\n ,\"microsoft.security/applications\": { \"SingularDisplayName\": \"Microsoft.Security application\" }\r\n ,\"microsoft.security/assessmentmetadata\": { \"SingularDisplayName\": \"Microsoft.Security assessment metadata\" }\r\n ,\"microsoft.security/assessments\": { \"SingularDisplayName\": \"Microsoft.Security assessment\" }\r\n ,\"microsoft.security/assessments/governanceassignments\": { \"SingularDisplayName\": \"Microsoft.Security assessments governance assignment\" }\r\n ,\"microsoft.security/assessments/subassessments\": { \"SingularDisplayName\": \"Microsoft.Security assessments sub assessment\" }\r\n ,\"microsoft.security/assignments\": { \"SingularDisplayName\": \"Microsoft.Security assignment\" }\r\n ,\"microsoft.security/automations\": { \"SingularDisplayName\": \"Microsoft.Security automation\" }\r\n ,\"microsoft.security/autoprovisioningsettings\": { \"SingularDisplayName\": \"Microsoft.Security auto provisioning setting\" }\r\n ,\"microsoft.security/complianceresults\": { \"SingularDisplayName\": \"Microsoft.Security compliance result\" }\r\n ,\"microsoft.security/compliances\": { \"SingularDisplayName\": \"Microsoft.Security compliance\" }\r\n ,\"microsoft.security/connectors\": { \"SingularDisplayName\": \"Microsoft.Security connector\" }\r\n ,\"microsoft.security/customassessmentautomations\": { \"SingularDisplayName\": \"Microsoft.Security custom assessment automation\" }\r\n ,\"microsoft.security/defenderforstoragesettings\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage setting\" }\r\n ,\"microsoft.security/defenderforstoragesettings/malwarescans\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage settings malware scan\" }\r\n ,\"microsoft.security/devicesecuritygroups\": { \"SingularDisplayName\": \"Microsoft.Security device security group\" }\r\n ,\"microsoft.security/governancerules\": { \"SingularDisplayName\": \"Microsoft.Security governance rule\" }\r\n ,\"microsoft.security/governancerules/operationresults\": { \"SingularDisplayName\": \"Microsoft.Security governance rules operation result\" }\r\n ,\"microsoft.security/healthreports\": { \"SingularDisplayName\": \"Microsoft.Security health report\" }\r\n ,\"microsoft.security/informationprotectionpolicies\": { \"SingularDisplayName\": \"Microsoft.Security information protection policy\" }\r\n ,\"microsoft.security/iotsecuritysolutions\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solution\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics model\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated alert\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated recommendation\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotalerttypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert type\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendationtypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation type\" }\r\n ,\"microsoft.security/locations/alerts\": { \"SingularDisplayName\": \"Security Alert\" }\r\n ,\"microsoft.security/mdeonboardings\": { \"SingularDisplayName\": \"Microsoft.Security mde onboarding\" }\r\n ,\"microsoft.security/pricings\": { \"SingularDisplayName\": \"Defender for Cloud\" }\r\n ,\"microsoft.security/pricings/securityoperators\": { \"SingularDisplayName\": \"Microsoft.Security pricings security operator\" }\r\n ,\"microsoft.security/regulatorycompliancestandards\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standard\" }\r\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance control\" }\r\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols/regulatorycomplianceassessments\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance controls regulatory compliance assessment\" }\r\n ,\"microsoft.security/securescores\": { \"SingularDisplayName\": \"Microsoft.Security secure score\" }\r\n ,\"microsoft.security/securityconnectors\": { \"SingularDisplayName\": \"Microsoft.Security security connector\" }\r\n ,\"microsoft.security/securityconnectors/devops\": { \"SingularDisplayName\": \"Microsoft.Security security connectors devop\" }\r\n ,\"microsoft.security/securitycontacts\": { \"SingularDisplayName\": \"Microsoft.Security security contact\" }\r\n ,\"microsoft.security/sensitivitysettings\": { \"SingularDisplayName\": \"Microsoft.Security sensitivity setting\" }\r\n ,\"microsoft.security/servervulnerabilityassessments\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessment\" }\r\n ,\"microsoft.security/servervulnerabilityassessmentssettings\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessments setting\" }\r\n ,\"microsoft.security/settings\": { \"SingularDisplayName\": \"Microsoft.Security setting\" }\r\n ,\"microsoft.security/standards\": { \"SingularDisplayName\": \"Microsoft.Security standard\" }\r\n ,\"microsoft.security/workspacesettings\": { \"SingularDisplayName\": \"Microsoft.Security workspace setting\" }\r\n ,\"microsoft.securitycopilot/capacities\": { \"SingularDisplayName\": \"Microsoft Security compute capacity\" }\r\n ,\"microsoft.securitydetonation/chambers\": { \"SingularDisplayName\": \"Security Detonation Chamber\" }\r\n ,\"microsoft.securityinsightsarg/sentinel\": { \"SingularDisplayName\": \"Microsoft Sentinel\" }\r\n ,\"microsoft.sentinelplatformservices/sentinelplatformservices\": { \"SingularDisplayName\": \"Microsoft.SentinelPlatformServices sentinel platform service\" }\r\n ,\"microsoft.serialconsole/consoleservices\": { \"SingularDisplayName\": \"Microsoft.SerialConsole console service\" }\r\n ,\"microsoft.serialconsole/serialports\": { \"SingularDisplayName\": \"Microsoft.SerialConsole serial port\" }\r\n ,\"microsoft.servicebus/namespaces\": { \"SingularDisplayName\": \"Service Bus namespace\" }\r\n ,\"microsoft.servicebus/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Service Bus Geo-DR Alias\" }\r\n ,\"microsoft.servicebus/namespaces/queues\": { \"SingularDisplayName\": \"Service Bus queue\" }\r\n ,\"microsoft.servicebus/namespaces/topics\": { \"SingularDisplayName\": \"Service Bus topic\" }\r\n ,\"microsoft.servicebus/namespaces/topics/subscriptions\": { \"SingularDisplayName\": \"Service Bus Subscription\" }\r\n ,\"microsoft.servicefabric/clusters\": { \"SingularDisplayName\": \"Service Fabric cluster\" }\r\n ,\"microsoft.servicefabric/managedclusters\": { \"SingularDisplayName\": \"Service Fabric managed cluster\" }\r\n ,\"microsoft.servicefabricmesh/applications\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh application\" }\r\n ,\"microsoft.servicefabricmesh/applications/services\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications service\" }\r\n ,\"microsoft.servicefabricmesh/applications/services/replicas\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications services replica\" }\r\n ,\"microsoft.servicefabricmesh/gateways\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh gateway\" }\r\n ,\"microsoft.servicefabricmesh/networks\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh network\" }\r\n ,\"microsoft.servicefabricmesh/secrets\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secret\" }\r\n ,\"microsoft.servicefabricmesh/secrets/values\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secrets value\" }\r\n ,\"microsoft.servicefabricmesh/volumes\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh volume\" }\r\n ,\"microsoft.servicelinker/dryruns\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker dryrun\" }\r\n ,\"microsoft.servicelinker/linkers\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker linker\" }\r\n ,\"microsoft.servicenetworking/trafficcontrollers\": { \"SingularDisplayName\": \"Application Gateway for Containers\" }\r\n ,\"microsoft.serviceshub/connectors\": { \"SingularDisplayName\": \"Services Hub Connector\" }\r\n ,\"microsoft.signalrservice/signalr\": { \"SingularDisplayName\": \"SignalR\" }\r\n ,\"microsoft.signalrservice/signalr/replicas\": { \"SingularDisplayName\": \"SignalR Replica\" }\r\n ,\"microsoft.signalrservice/webpubsub\": { \"SingularDisplayName\": \"Web PubSub Service\" }\r\n ,\"microsoft.signalrservice/webpubsub/replicas\": { \"SingularDisplayName\": \"Web PubSub Service Replica\" }\r\n ,\"microsoft.skytap/billingnodes\": { \"SingularDisplayName\": \"Microsoft.Skytap billing node\" }\r\n ,\"microsoft.skytap/interfaces\": { \"SingularDisplayName\": \"Microsoft.Skytap interface\" }\r\n ,\"microsoft.skytap/nodes\": { \"SingularDisplayName\": \"Microsoft.Skytap node\" }\r\n ,\"microsoft.softwareplan/hybridusebenefits\": { \"SingularDisplayName\": \"Microsoft.SoftwarePlan hybrid use benefit\" }\r\n ,\"microsoft.solutions/applicationdefinitions\": { \"SingularDisplayName\": \"Service catalog managed application definition\" }\r\n ,\"microsoft.solutions/applications\": { \"SingularDisplayName\": \"Managed application\" }\r\n ,\"microsoft.solutions/jitrequests\": { \"SingularDisplayName\": \"Microsoft.Solutions JIT request\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts\": { \"SingularDisplayName\": \"Landing zone account\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\r\n ,\"microsoft.sovereign/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\r\n ,\"microsoft.sovereign/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\r\n ,\"microsoft.sovereign/transparencylogs\": { \"SingularDisplayName\": \"Transparency log\" }\r\n ,\"microsoft.sql/azuresql\": { \"SingularDisplayName\": \"Azure SQL resource\" }\r\n ,\"microsoft.sql/instancepools\": { \"SingularDisplayName\": \"Instance pool\" }\r\n ,\"microsoft.sql/managedinstances\": { \"SingularDisplayName\": \"SQL managed instance\" }\r\n ,\"microsoft.sql/managedinstances/databases\": { \"SingularDisplayName\": \"Managed database\" }\r\n ,\"microsoft.sql/servers\": { \"SingularDisplayName\": \"SQL server\" }\r\n ,\"microsoft.sql/servers/databases\": { \"SingularDisplayName\": \"SQL database\" }\r\n ,\"microsoft.sql/servers/elasticpools\": { \"SingularDisplayName\": \"SQL elastic pool\" }\r\n ,\"microsoft.sql/servers/jobagents\": { \"SingularDisplayName\": \"Elastic Job agent\" }\r\n ,\"microsoft.sql/virtualclusters\": { \"SingularDisplayName\": \"Virtual cluster\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine group\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups/availabilitygrouplisteners\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine groups availability group listener\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachines\": { \"SingularDisplayName\": \"SQL virtual machine\" }\r\n ,\"microsoft.standbypool/standbycontainergrouppools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pool\" }\r\n ,\"microsoft.standbypool/standbycontainergrouppools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pools runtime view\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pool\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools runtime view\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools/standbyvirtualmachines\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools standby virtual machine\" }\r\n ,\"microsoft.storage/storageaccounts\": { \"SingularDisplayName\": \"Storage account\" }\r\n ,\"microsoft.storageactions/storagetasks\": { \"SingularDisplayName\": \"Storage task - Azure Storage Actions\" }\r\n ,\"microsoft.storagecache/amlfilesystems\": { \"SingularDisplayName\": \"Azure Managed Lustre\" }\r\n ,\"microsoft.storagecache/caches\": { \"SingularDisplayName\": \"HPC cache\" }\r\n ,\"microsoft.storagediscovery/storagediscoveryworkspaces\": { \"SingularDisplayName\": \"Storage Discovery workspace\" }\r\n ,\"microsoft.storagehub/all\": { \"SingularDisplayName\": \"All resources\" }\r\n ,\"microsoft.storagehub/policycomplianceresources\": { \"SingularDisplayName\": \"Policy compliance\" }\r\n ,\"microsoft.storageinsights/storagecollectionrules\": { \"SingularDisplayName\": \"Microsoft.StorageInsights storage collection rule\" }\r\n ,\"microsoft.storagemover/storagemovers\": { \"SingularDisplayName\": \"Storage mover\" }\r\n ,\"microsoft.storagepool/diskpools\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pool\" }\r\n ,\"microsoft.storagepool/diskpools/iscsitargets\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pools iscsi target\" }\r\n ,\"microsoft.storagesync/storagesyncservices\": { \"SingularDisplayName\": \"Storage Sync Service\" }\r\n ,\"microsoft.storagetasks/storagetasks\": { \"SingularDisplayName\": \"Microsoft.StorageTasks storage task\" }\r\n ,\"microsoft.storsimple/managers\": { \"SingularDisplayName\": \"StorSimple device manager\" }\r\n ,\"microsoft.storsimple/managers/accesscontrolrecords\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers access control record\" }\r\n ,\"microsoft.storsimple/managers/bandwidthsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers bandwidth setting\" }\r\n ,\"microsoft.storsimple/managers/certificates\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers certificate\" }\r\n ,\"microsoft.storsimple/managers/devices\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers device\" }\r\n ,\"microsoft.storsimple/managers/devices/alertsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices alert setting\" }\r\n ,\"microsoft.storsimple/managers/devices/backuppolicies\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policy\" }\r\n ,\"microsoft.storsimple/managers/devices/backuppolicies/schedules\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policies schedule\" }\r\n ,\"microsoft.storsimple/managers/devices/backupschedulegroups\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup schedule group\" }\r\n ,\"microsoft.storsimple/managers/devices/chapsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices chap setting\" }\r\n ,\"microsoft.storsimple/managers/devices/fileservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileserver\" }\r\n ,\"microsoft.storsimple/managers/devices/fileservers/shares\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileservers share\" }\r\n ,\"microsoft.storsimple/managers/devices/iscsiservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiserver\" }\r\n ,\"microsoft.storsimple/managers/devices/iscsiservers/disks\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiservers disk\" }\r\n ,\"microsoft.storsimple/managers/devices/jobs\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices job\" }\r\n ,\"microsoft.storsimple/managers/devices/networksettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices network setting\" }\r\n ,\"microsoft.storsimple/managers/devices/securitysettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices security setting\" }\r\n ,\"microsoft.storsimple/managers/devices/timesettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices time setting\" }\r\n ,\"microsoft.storsimple/managers/devices/updatesummary\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices update summary\" }\r\n ,\"microsoft.storsimple/managers/devices/volumecontainers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume container\" }\r\n ,\"microsoft.storsimple/managers/devices/volumecontainers/volumes\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume containers volume\" }\r\n ,\"microsoft.storsimple/managers/encryptionsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers encryption setting\" }\r\n ,\"microsoft.storsimple/managers/extendedinformation\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers extended information\" }\r\n ,\"microsoft.storsimple/managers/storageaccountcredentials\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage account credential\" }\r\n ,\"microsoft.storsimple/managers/storagedomains\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage domain\" }\r\n ,\"microsoft.streamanalytics/clusters\": { \"SingularDisplayName\": \"Stream Analytics cluster\" }\r\n ,\"microsoft.streamanalytics/streamingjobs\": { \"SingularDisplayName\": \"Stream Analytics job\" }\r\n ,\"microsoft.subscription/aliases\": { \"SingularDisplayName\": \"Microsoft.Subscription aliase\" }\r\n ,\"microsoft.subscription/changetenantrequest\": { \"SingularDisplayName\": \"Microsoft.Subscription change tenant request\" }\r\n ,\"microsoft.subscription/policies\": { \"SingularDisplayName\": \"Microsoft.Subscription policy\" }\r\n ,\"microsoft.subscription/subscriptiondefinitions\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription definition\" }\r\n ,\"microsoft.subscription/subscriptionoperations\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription operation\" }\r\n ,\"microsoft.support/fileworkspaces\": { \"SingularDisplayName\": \"Microsoft.Support file workspace\" }\r\n ,\"microsoft.support/fileworkspaces/files\": { \"SingularDisplayName\": \"Microsoft.Support file workspaces file\" }\r\n ,\"microsoft.support/services\": { \"SingularDisplayName\": \"Microsoft.Support service\" }\r\n ,\"microsoft.support/services/problemclassifications\": { \"SingularDisplayName\": \"Microsoft.Support services problem classification\" }\r\n ,\"microsoft.support/supporttickets\": { \"SingularDisplayName\": \"Support Request\" }\r\n ,\"microsoft.sustainabilityservices/calculations\": { \"SingularDisplayName\": \"Project Sustainability Calculator\" }\r\n ,\"microsoft.symphony/instances\": { \"SingularDisplayName\": \"Microsoft.Symphony instance\" }\r\n ,\"microsoft.symphony/solutions\": { \"SingularDisplayName\": \"Microsoft.Symphony solution\" }\r\n ,\"microsoft.symphony/targets\": { \"SingularDisplayName\": \"Microsoft.Symphony target\" }\r\n ,\"microsoft.synapse/privatelinkhubs\": { \"SingularDisplayName\": \"Synapse private link hub\" }\r\n ,\"microsoft.synapse/workspaces\": { \"SingularDisplayName\": \"Synapse workspace\" }\r\n ,\"microsoft.synapse/workspaces/bigdatapools\": { \"SingularDisplayName\": \"Apache Spark pool\" }\r\n ,\"microsoft.synapse/workspaces/kustopools\": { \"SingularDisplayName\": \"Data Explorer pool\" }\r\n ,\"microsoft.synapse/workspaces/kustopools/databases\": { \"SingularDisplayName\": \"Data Explorer Database\" }\r\n ,\"microsoft.synapse/workspaces/scopepools\": { \"SingularDisplayName\": \"SCOPE pool\" }\r\n ,\"microsoft.synapse/workspaces/sqlpools\": { \"SingularDisplayName\": \"Dedicated SQL pool\" }\r\n ,\"microsoft.syntex/accounts\": { \"SingularDisplayName\": \"Microsoft.Syntex account\" }\r\n ,\"microsoft.syntex/documentprocessors\": { \"SingularDisplayName\": \"Microsoft.Syntex document processor\" }\r\n ,\"microsoft.test/healthdataaiservices\": { \"SingularDisplayName\": \"Azure Health Data and AI Services\" }\r\n ,\"microsoft.timeseriesinsights/environments\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environment\" }\r\n ,\"microsoft.timeseriesinsights/environments/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments access policy\" }\r\n ,\"microsoft.timeseriesinsights/environments/eventsources\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments event source\" }\r\n ,\"microsoft.timeseriesinsights/environments/referencedatasets\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments reference data set\" }\r\n ,\"microsoft.toolchainorchestrator/activations\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator activation\" }\r\n ,\"microsoft.toolchainorchestrator/campaigns\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaign\" }\r\n ,\"microsoft.toolchainorchestrator/campaigns/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaigns version\" }\r\n ,\"microsoft.toolchainorchestrator/catalogs\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalog\" }\r\n ,\"microsoft.toolchainorchestrator/catalogs/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalogs version\" }\r\n ,\"microsoft.toolchainorchestrator/diagnostics\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator diagnostic\" }\r\n ,\"microsoft.toolchainorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instance\" }\r\n ,\"microsoft.toolchainorchestrator/instances/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instances version\" }\r\n ,\"microsoft.toolchainorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solution\" }\r\n ,\"microsoft.toolchainorchestrator/solutions/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solutions version\" }\r\n ,\"microsoft.toolchainorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator target\" }\r\n ,\"microsoft.toolchainorchestrator/targets/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator targets version\" }\r\n ,\"microsoft.updatemanager/updaterules\": { \"SingularDisplayName\": \"Update Rule\" }\r\n ,\"microsoft.usagebilling/accounts\": { \"SingularDisplayName\": \"Microsoft.UsageBilling account\" }\r\n ,\"microsoft.usagebilling/accounts/dataexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts data export\" }\r\n ,\"microsoft.usagebilling/accounts/inputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts input\" }\r\n ,\"microsoft.usagebilling/accounts/metricexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts metric export\" }\r\n ,\"microsoft.usagebilling/accounts/pav2outputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pav2output\" }\r\n ,\"microsoft.usagebilling/accounts/pipelines\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipeline\" }\r\n ,\"microsoft.usagebilling/accounts/pipelines/outputselectors\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipelines output selector\" }\r\n ,\"microsoft.verifiedid/authorities\": { \"SingularDisplayName\": \"Microsoft.VerifiedId authority\" }\r\n ,\"microsoft.videoindexer/accounts\": { \"SingularDisplayName\": \"Azure AI Video Indexer\" }\r\n ,\"microsoft.virtualmachineimages/imagetemplates\": { \"SingularDisplayName\": \"Image template\" }\r\n ,\"microsoft.visualstudio/account\": { \"SingularDisplayName\": \"Azure DevOps organization\" }\r\n ,\"microsoft.vmware/resourcepools\": { \"SingularDisplayName\": \"Microsoft.VMware resource pool\" }\r\n ,\"microsoft.vmware/vcenters\": { \"SingularDisplayName\": \"Microsoft.VMware vcenter\" }\r\n ,\"microsoft.vmware/vcenters/inventoryitems\": { \"SingularDisplayName\": \"Microsoft.VMware vcenters inventory item\" }\r\n ,\"microsoft.vmware/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine\" }\r\n ,\"microsoft.vmware/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine template\" }\r\n ,\"microsoft.vmware/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.VMware virtual network\" }\r\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudnodes\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud node\" }\r\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudservices\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud service\" }\r\n ,\"microsoft.vmwarecloudsimple/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple virtual machine\" }\r\n ,\"microsoft.vnfmanager/devices\": { \"SingularDisplayName\": \"Microsoft.VnfManager device\" }\r\n ,\"microsoft.vnfmanager/vendors\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendor\" }\r\n ,\"microsoft.vnfmanager/vendors/skus\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendors SKU\" }\r\n ,\"microsoft.vnfmanager/vnfs\": { \"SingularDisplayName\": \"Microsoft.VnfManager vnf\" }\r\n ,\"microsoft.voiceservices/communicationsgateways\": { \"SingularDisplayName\": \"Communications Gateway\" }\r\n ,\"microsoft.voiceservices/communicationsgateways/testlines\": { \"SingularDisplayName\": \"Communications Gateway Test Line\" }\r\n ,\"microsoft.vsonline/accounts\": { \"SingularDisplayName\": \"Microsoft.VSOnline account\" }\r\n ,\"microsoft.vsonline/plans\": { \"SingularDisplayName\": \"Visual Studio Online Plan\" }\r\n ,\"microsoft.web/certificates\": { \"SingularDisplayName\": \"Microsoft.Web certificate\" }\r\n ,\"microsoft.web/connectiongateways\": { \"SingularDisplayName\": \"App Service on-premises data gateway\" }\r\n ,\"microsoft.web/connections\": { \"SingularDisplayName\": \"App Service API connection\" }\r\n ,\"microsoft.web/containerapps\": { \"SingularDisplayName\": \"Microsoft.Web container app\" }\r\n ,\"microsoft.web/containerapps/revisions\": { \"SingularDisplayName\": \"Microsoft.Web container apps revision\" }\r\n ,\"microsoft.web/customapis\": { \"SingularDisplayName\": \"Logic apps custom connector\" }\r\n ,\"microsoft.web/deletedsites\": { \"SingularDisplayName\": \"Microsoft.Web deleted site\" }\r\n ,\"microsoft.web/hostingenvironments\": { \"SingularDisplayName\": \"App Service Environment\" }\r\n ,\"microsoft.web/ishostingenvironmentnameavailable\": { \"SingularDisplayName\": \"Microsoft.Web ishostingenvironmentnameavailable\" }\r\n ,\"microsoft.web/kubeenvironments\": { \"SingularDisplayName\": \"App Service Kubernetes Environment\" }\r\n ,\"microsoft.web/logicappstemplate\": { \"SingularDisplayName\": \"Logic Apps Template\" }\r\n ,\"microsoft.web/publishingusers\": { \"SingularDisplayName\": \"Microsoft.Web publishing user\" }\r\n ,\"microsoft.web/serverfarms\": { \"SingularDisplayName\": \"App Service plan\" }\r\n ,\"microsoft.web/sites\": { \"SingularDisplayName\": \"App Service web app\" }\r\n ,\"microsoft.web/sites/slots\": { \"SingularDisplayName\": \"App Service deployment slot\" }\r\n ,\"microsoft.web/sourcecontrols\": { \"SingularDisplayName\": \"Microsoft.Web sourcecontrol\" }\r\n ,\"microsoft.web/staticsites\": { \"SingularDisplayName\": \"Static Web App\" }\r\n ,\"microsoft.weightsandbiases/instances\": { \"SingularDisplayName\": \"Azure Native Weights & Biases Cloud Service\" }\r\n ,\"microsoft.whiteboxcadlprovider/whiteboxresources\": { \"SingularDisplayName\": \"Microsoft.WhiteBoxCadlProvider white box resource\" }\r\n ,\"microsoft.windows365/cloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.Windows365 cloud pc delegated msi\" }\r\n ,\"microsoft.windowsesu/multipleactivationkeys\": { \"SingularDisplayName\": \"Microsoft.WindowsESU multiple activation key\" }\r\n ,\"microsoft.windowsiot/deviceservices\": { \"SingularDisplayName\": \"Microsoft.WindowsIoT device service\" }\r\n ,\"microsoft.windowspushnotificationservices/registrations\": { \"SingularDisplayName\": \"Windows Push Notification Service\" }\r\n ,\"microsoft.workloadmonitor/monitors\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitor\" }\r\n ,\"microsoft.workloadmonitor/monitors/history\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitors history\" }\r\n ,\"microsoft.workloads/configurationvalidationresults\": { \"SingularDisplayName\": \"Microsoft.Workloads configuration validation result\" }\r\n ,\"microsoft.workloads/connectors\": { \"SingularDisplayName\": \"Microsoft.Workloads connector\" }\r\n ,\"microsoft.workloads/connectors/acssbackups\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors acss backup\" }\r\n ,\"microsoft.workloads/connectors/amsinsights\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors ams insight\" }\r\n ,\"microsoft.workloads/connectors/sapvirtualinstancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors sap virtual instance monitor\" }\r\n ,\"microsoft.workloads/epicvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for Epic solution\" }\r\n ,\"microsoft.workloads/insights\": { \"SingularDisplayName\": \"Microsoft.Workloads insight\" }\r\n ,\"microsoft.workloads/instancegroupmonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance group monitor\" }\r\n ,\"microsoft.workloads/instancehealthdefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definition\" }\r\n ,\"microsoft.workloads/instancehealthdefinitions/signaldefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definitions signal definition\" }\r\n ,\"microsoft.workloads/instancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance monitor\" }\r\n ,\"microsoft.workloads/monitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP solutions\" }\r\n ,\"microsoft.workloads/oraclevirtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instance\" }\r\n ,\"microsoft.workloads/oraclevirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instances database instance\" }\r\n ,\"microsoft.workloads/phpworkloads\": { \"SingularDisplayName\": \"Microsoft.Workloads php workload\" }\r\n ,\"microsoft.workloads/phpworkloads/wordpressinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads php workloads wordpress instance\" }\r\n ,\"microsoft.workloads/sapdiscoverysites\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery site\" }\r\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instance\" }\r\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances/serverinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instances server instance\" }\r\n ,\"microsoft.workloads/sapvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/applicationinstances\": { \"SingularDisplayName\": \"App server instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/centralinstances\": { \"SingularDisplayName\": \"Central service instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Database for SAP solutions\" }\r\n ,\"microsoft.workloads/virtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instance\" }\r\n ,\"microsoft.workloads/virtualinstances/components\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instances component\" }\r\n ,\"microsoft.workloads/workloadinstance\": { \"SingularDisplayName\": \"My Resource\" }\r\n ,\"microsoft.zerotrustsegmentation/segmentationmanagers\": { \"SingularDisplayName\": \"Segmentation Manager\" }\r\n ,\"mongodb.atlas/organizations\": { \"SingularDisplayName\": \"MongoDB Atlas Organization\" }\r\n ,\"neon.postgres/organizations\": { \"SingularDisplayName\": \"Neon Serverless Postgres Organization\" }\r\n ,\"newrelic.observability/monitors\": { \"SingularDisplayName\": \"New Relic\" }\r\n ,\"nginx.nginxplus/nginxdeployments\": { \"SingularDisplayName\": \"NGINXaaS\" }\r\n ,\"oracle.database/autonomousdatabases\": { \"SingularDisplayName\": \"Autonomous Database\" }\r\n ,\"oracle.database/basedb\": { \"SingularDisplayName\": \"Autonomous Database\" }\r\n ,\"oracle.database/cloudexadatainfrastructures\": { \"SingularDisplayName\": \"Oracle Exadata Infrastructure\" }\r\n ,\"oracle.database/cloudvmclusters\": { \"SingularDisplayName\": \"Oracle Exadata VM Cluster\" }\r\n ,\"oracle.database/exadbvmclusters\": { \"SingularDisplayName\": \"Oracle Exascale VM Cluster\" }\r\n ,\"oracle.database/exascaledbstoragevaults\": { \"SingularDisplayName\": \"Oracle Exascale DB Storage Vault\" }\r\n ,\"oracle.database/networkanchors\": { \"SingularDisplayName\": \"Network Anchor\" }\r\n ,\"oracle.database/oraclesubscriptions\": { \"SingularDisplayName\": \"OracleSubscription\" }\r\n ,\"oracle.database/resourceanchors\": { \"SingularDisplayName\": \"Resource Anchor\" }\r\n ,\"paloaltonetworks.cloudngfw/firewalls\": { \"SingularDisplayName\": \"Cloud NGFW by Palo Alto Networks\" }\r\n ,\"paloaltonetworks.cloudngfw/globalrulestacks\": { \"SingularDisplayName\": \"Global Rulestack\" }\r\n ,\"paloaltonetworks.cloudngfw/localrulestacks\": { \"SingularDisplayName\": \"Local Rulestack for Cloud NGFW by Palo Alto Networks\" }\r\n ,\"pinecone.vectordb/organizations\": { \"SingularDisplayName\": \"Azure Native Pinecone Cloud Service\" }\r\n ,\"purestorage.block/reservations\": { \"SingularDisplayName\": \"Azure Native Pure Storage Cloud Service\" }\r\n ,\"purestorage.block/storagepools\": { \"SingularDisplayName\": \"Storage pool\" }\r\n ,\"purestorage.block/storagepools/avsstoragecontainers\": { \"SingularDisplayName\": \"PureStorage.Block storage pools avs storage container\" }\r\n })[tolower(id)]\r\n}\r\n", + "$fxv#4": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_5(id: string) {\r\n dynamic({\r\n \"qumulo.qaas/storages\": { \"SingularDisplayName\": \"Qumulo.QaaS storage\" }\r\n ,\"qumulo.storage/filesystems\": { \"SingularDisplayName\": \"Azure Native Qumulo Scalable File Service\" }\r\n ,\"solarwinds.observability/organizations\": { \"SingularDisplayName\": \"SolarWinds Observability\" }\r\n ,\"splitio.experimentation/experimentationworkspaces\": { \"SingularDisplayName\": \"Split Experimentation Workspace\" }\r\n ,\"wandisco.fusion/migrators\": { \"SingularDisplayName\": \"LiveData Migrator\" }\r\n ,\"wandisco.fusion/migrators/datatransferagents\": { \"SingularDisplayName\": \"Data Transfer Agent\" }\r\n ,\"wandisco.fusion/migrators/exclusiontemplates\": { \"SingularDisplayName\": \"Exclusion\" }\r\n ,\"wandisco.fusion/migrators/livedatamigrations\": { \"SingularDisplayName\": \"Migration\" }\r\n ,\"wandisco.fusion/migrators/metadatamigrations\": { \"SingularDisplayName\": \"Metadata Migration\" }\r\n ,\"wandisco.fusion/migrators/metadatatargets\": { \"SingularDisplayName\": \"Metadata Target\" }\r\n ,\"wandisco.fusion/migrators/pathmappings\": { \"SingularDisplayName\": \"Path Mapping\" }\r\n ,\"wandisco.fusion/migrators/targets\": { \"SingularDisplayName\": \"Target\" }\r\n ,\"wandisco.fusion/migrators/verifications\": { \"SingularDisplayName\": \"Verification\" }\r\n })[tolower(id)]\r\n}\r\n", + "$fxv#5": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n// resource_type\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData')\r\nresource_type(id: string) {\r\n coalesce(_resource_type_1(id), _resource_type_2(id), _resource_type_3(id), _resource_type_4(id), _resource_type_5(id))\r\n}\r\n", + "$fxv#6": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Common utility functions\r\n//\r\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\r\n//======================================================================================================================\r\n\r\n\r\n//===| Date functions |=================================================================================================\r\n\r\n// monthstring\r\n.create-or-alter function \r\nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \r\nmonthstring(['date']: datetime, length: int = 9)\r\n{\r\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\r\n}\r\n\r\n// datestring\r\n.create-or-alter function \r\nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \r\ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n let month = (d: datetime) { monthstring(d, 3) };\r\n let endDate = iff(end == datetime('0001-01-01'), start, end);\r\n let sameDate = startofday(start) == startofday(endDate);\r\n let sameMonth = startofmonth(start) == startofmonth(endDate);\r\n let sameYear = startofyear(start) == startofyear(endDate);\r\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\r\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\r\n let currentYear = sameYear and startofyear(start) == startofyear(now());\r\n case(\r\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\r\n fullYear,\r\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\r\n // 1 full mo, same year | Mmm yyyy\r\n fullMonth and sameMonth and sameYear,\r\n strcat(month(start), ' ', getyear(start)),\r\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\r\n fullMonth and sameYear,\r\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\r\n fullMonth and not(sameYear),\r\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\r\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\r\n sameDate,\r\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\r\n not(fullMonth) and sameMonth and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\r\n not(fullMonth) and not(sameMonth) and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\r\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\r\n )\r\n}\r\n\r\n// daterange\r\n.create-or-alter function \r\nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \r\ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n datestring(start, end)\r\n}\r\n\r\n// monthsago\r\n.create-or-alter function \r\nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\r\nmonthsago(months: int)\r\n{\r\n datetime_add('month', -months, startofmonth(now()))\r\n}\r\n\r\n\r\n//===| Number functions |===============================================================================================\r\n// NOTE: Must be defined before string converters\r\n\r\n// delta\r\n.create-or-alter function \r\nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \r\ndelta(oldval: double, newval: double)\r\n{\r\n (newval - todouble(oldval))/oldval\r\n}\r\n\r\n// percentOfTotal\r\n// NOTE: Must be before percent() function\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercentOfTotal(t: (Count: long), tot: long)\r\n{\r\n let total = todouble(tot);\r\n t \r\n | extend Percent = round(Count / total * 100, 3) \r\n | order by Count desc\r\n}\r\n\r\n// percent\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercent(t: (Count: long))\r\n{\r\n let total = todouble(toscalar(t | summarize sum(Count)));\r\n percentOfTotal(t, total)\r\n}\r\n\r\n// plusminus\r\n.create-or-alter function \r\nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\r\nplusminus(val: string)\r\n{\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, val, strcat('+', val))\r\n}\r\n\r\n// updown\r\n.create-or-alter function \r\nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\r\nupdown(val: string)\r\n{\r\n // TODO: Handle 0\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\r\n}\r\n\r\n\r\n//===| String functions |===============================================================================================\r\n\r\n// percentstring\r\n// NOTE: Must be defined before deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\r\npercentstring(num: double, total: double = 1.0, places: int = 9)\r\n{\r\n let value = 1.0 * num / total * 100;\r\n strcat(case(\r\n places != 9, round(value, places),\r\n value < 10, round(value, 2),\r\n round(value, 1)\r\n ), '%')\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// arraystring\r\n.create-or-alter function \r\nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\r\narraystring(arr: dynamic)\r\n{\r\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\r\n tostring(arr)\r\n , @'^\\[\"', '')\r\n , @'\"\\]$', '')\r\n , @'^, ', '')\r\n , @', $', '')\r\n , @'^\\[]$', '')\r\n , '\",\"', ', ')\r\n}\r\n\r\n// deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\r\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\r\n{\r\n let d = delta(oldval, newval);\r\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\r\n}\r\n\r\n// diffstring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\r\ndiffstring(oldval: double, newval: double, places: int = 1)\r\n{\r\n plusminus(round(newval - oldval, places))\r\n}\r\n\r\n// numberstring\r\n.create-or-alter function \r\nwith (docstring = 'Convert a number to a string', folder = 'Common')\r\nnumberstring(num: double, abbrev: bool = true)\r\n{\r\n replace_regex(case(\r\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\r\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\r\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\r\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\r\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\r\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\r\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\r\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\r\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\r\n tostring(num)\r\n ), @'\\.0$', '')\r\n}\r\n\r\n\r\n//===| Other |==========================================================================================================\r\n\r\n// ifempty\r\n.create-or-alter function \r\nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\r\nifempty(val: dynamic, defaultVal: dynamic)\r\n{\r\n iff(isempty(val), defaultVal, val)\r\n}\r\n", + "$fxv#7": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Settings |=======================================================================================================\r\n\r\n.create-merge table HubSettingsLog (\r\n version: string,\r\n scopes: dynamic,\r\n retention: dynamic\r\n)\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// HubSettings function\r\n.create-or-alter function\r\nwith (docstring='Gets the latest version of hub settings.', folder='Settings')\r\nHubSettings()\r\n{\r\n HubSettingsLog\r\n | extend timestamp = ingestion_time()\r\n | summarize arg_max(timestamp, *)\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// HubScopes function\r\n.create-or-alter function\r\nwith (docstring='Gets the currently configured scopes.', folder='Settings')\r\nHubScopes()\r\n{\r\n HubSettings\r\n | project scopes\r\n | mv-expand scopes\r\n}\r\n\r\n\r\n//===| Open data |======================================================================================================\r\n\r\n// PricingUnits -- Create table if it doesn't exist\r\n.create-merge table PricingUnits ( ignore: string )\r\n\r\n// PricingUnits -- Remove all columns\r\n.alter table PricingUnits ( ignore: string )\r\n\r\n// PricingUnits -- Redefine all columns to change types\r\n.alter table PricingUnits (\r\n x_PricingUnitDescription: string,\r\n x_PricingBlockSize: real,\r\n PricingUnit: string\r\n)\r\n\r\n// Regions\r\n.create-merge table Regions(\r\n ResourceLocation: string,\r\n RegionId: string,\r\n RegionName: string\r\n)\r\n\r\n// ResourceTypes\r\n.create-merge table ResourceTypes(\r\n x_ResourceType: string,\r\n SingularDisplayName: string,\r\n PluralDisplayName: string,\r\n LowerSingularDisplayName: string,\r\n LowerPluralDisplayName: string,\r\n IsPreview: bool,\r\n Description: string,\r\n IconUri: string\r\n)\r\n\r\n// Services\r\n.create-merge table Services(\r\n x_ConsumedService: string,\r\n x_ResourceType: string,\r\n ServiceName: string,\r\n ServiceCategory: string,\r\n ServiceSubcategory: string,\r\n PublisherName: string,\r\n x_PublisherCategory: string,\r\n x_Environment: string,\r\n x_ServiceModel: string\r\n)\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// parse_resourceid\r\n.create-or-alter function\r\nwith (docstring = 'Parses an Azure resource ID to extract resource attributes like the name, type, resource group, and subaccount ID.', folder = 'Common')\r\nparse_resourceid(resourceId: string) {\r\n let ResourceId = tolower(resourceId);\r\n // let ResourceId = tolower('/providers/Microsoft.BillingBenefits/savingsPlanOrders/2d2e284b-0638-427e-b8c6-1b874d4f17c8/sp/xxx');\r\n let SubAccountId = tostring(extract('/subscriptions/[^/]+', 1, ResourceId));\r\n let x_ResourceGroupName = tostring(extract('/resourcegroups/[^/]+', 1, ResourceId));\r\n let providerPath = iff(ResourceId !contains '/providers/', '', split(iff(ResourceId startswith '/subscriptions/', strcat('/providers/microsoft.resources/', ResourceId), ResourceId), '/providers/')[-1]);\r\n let x_ResourceProvider = iff(isempty(providerPath), '', split(providerPath, '/')[0]);\r\n let tmp_ResourceProviderPath = iff(isempty(providerPath), '', substring(providerPath, strlen(x_ResourceProvider) + 1));\r\n let segments = split(tmp_ResourceProviderPath, '/');\r\n let ResourceName = trim(@'/+', replace_string(strcat_array(array_iff(\r\n dynamic([false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true]),\r\n segments, dynamic([])), '/'), '//', '/'));\r\n let x_ResourceTypePath = trim(@'/+', replace_string(strcat_array(array_iff(\r\n dynamic([true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]),\r\n segments, dynamic([])), '/'), '//', '/'));\r\n let xRT = iff(isempty(x_ResourceProvider) or isempty(x_ResourceTypePath), '', strcat(x_ResourceProvider, '/', x_ResourceTypePath));\r\n // TODO: Remove ResourceType in 0.9\r\n bag_pack('ResourceId', ResourceId, 'ResourceName', ResourceName, 'ResourceType', xRT, 'SubAccountId', SubAccountId, 'x_ResourceGroupName', x_ResourceGroupName, 'x_ResourceProvider', x_ResourceProvider, 'x_ResourceType', xRT)\r\n}\r\n", + "$fxv#8": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| ActualCosts |====================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_raw table -- Create the table if it doesn't exist\r\n.create-merge table ActualCosts_raw ( ignore: string )\r\n\r\n// ActualCosts_raw table -- Remove all columns to allow changing column types\r\n.alter table ActualCosts_raw ( ignore: string )\r\n\r\n// ActualCosts_raw table -- Redefine all columns\r\n.alter table ActualCosts_raw (\r\n AccountName: string,\r\n AccountOwnerId: string,\r\n AdditionalInfo: string,\r\n AvailabilityZone: string,\r\n BillingAccountId: string, \r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n BillingPeriodEndDate: datetime,\r\n BillingPeriodStartDate: datetime,\r\n BillingProfileId: string,\r\n BillingProfileName: string,\r\n ChargeType: string,\r\n ConsumedService: string,\r\n CostCenter: string,\r\n Cost: real,\r\n Date: datetime,\r\n EffectivePrice: real,\r\n Frequency: string,\r\n InvoiceSection: string,\r\n InvoiceSectionId: string,\r\n IsAzureCreditEligible: bool,\r\n MeterCategory: string,\r\n MeterId: string,\r\n MeterName: string,\r\n MeterRegion: string,\r\n MeterSubCategory: string,\r\n OfferId: string,\r\n PartNumber: string,\r\n PlanName: string,\r\n Product: string,\r\n ProductOrderId: string,\r\n ProductOrderName: string,\r\n PublisherName: string,\r\n PublisherType: string,\r\n Quantity: real,\r\n ReservationId: string,\r\n ReservationName: string,\r\n ResourceGroup: string,\r\n ResourceId: string,\r\n ResourceLocation: string,\r\n ResourceName: string,\r\n ServiceFamily: string,\r\n ServiceInfo1: string,\r\n ServiceInfo2: string,\r\n SubscriptionId: string,\r\n SubscriptionName: string,\r\n Tags: string,\r\n Term: string,\r\n UnitOfMeasure: string,\r\n UnitPrice: real\r\n)\r\n\r\n// ActualCosts_raw ingestion mapping\r\n.create-or-alter table ActualCosts_raw ingestion parquet mapping \"ActualCosts_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\r\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\r\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\r\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\r\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\r\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\r\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\r\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\r\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\r\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\r\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\r\n]\r\n```\r\n\r\n// ActualCosts_raw retention policy (clear historical data)\r\n.alter-merge table ActualCosts_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// ActualCosts_raw retention policy (set the user-defined retention period)\r\n.alter-merge table ActualCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable ActualCosts_raw streaming ingestion (required for Fabric)\r\n.alter table ActualCosts_raw policy streamingingestion disable\r\n\r\n\r\n//===| AmortizedCosts |=================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_raw table -- Create the table if it doesn't exist\r\n.create-merge table AmortizedCosts_raw ( ignore: string )\r\n\r\n// AmortizedCosts_raw table -- Remove all columns to allow changing column types\r\n.alter table AmortizedCosts_raw ( ignore: string )\r\n\r\n// AmortizedCosts_raw table -- Redefine all columns\r\n.alter table AmortizedCosts_raw (\r\n AccountName: string,\r\n AccountOwnerId: string,\r\n AdditionalInfo: string,\r\n AvailabilityZone: string,\r\n BillingAccountId: string, \r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n BillingPeriodEndDate: datetime,\r\n BillingPeriodStartDate: datetime,\r\n BillingProfileId: string,\r\n BillingProfileName: string,\r\n ChargeType: string,\r\n ConsumedService: string,\r\n CostCenter: string,\r\n Cost: real,\r\n Date: datetime,\r\n EffectivePrice: real,\r\n Frequency: string,\r\n InvoiceSection: string,\r\n InvoiceSectionId: string,\r\n IsAzureCreditEligible: bool,\r\n MeterCategory: string,\r\n MeterId: string,\r\n MeterName: string,\r\n MeterRegion: string,\r\n MeterSubCategory: string,\r\n OfferId: string,\r\n PartNumber: string,\r\n PlanName: string,\r\n Product: string,\r\n ProductOrderId: string,\r\n ProductOrderName: string,\r\n PublisherName: string,\r\n PublisherType: string,\r\n Quantity: real,\r\n ReservationId: string,\r\n ReservationName: string,\r\n ResourceGroup: string,\r\n ResourceId: string,\r\n ResourceLocation: string,\r\n ResourceName: string,\r\n ServiceFamily: string,\r\n ServiceInfo1: string,\r\n ServiceInfo2: string,\r\n SubscriptionId: string,\r\n SubscriptionName: string,\r\n Tags: string,\r\n Term: string,\r\n UnitOfMeasure: string,\r\n UnitPrice: real\r\n)\r\n\r\n// AmortizedCosts_raw ingestion mapping\r\n.create-or-alter table AmortizedCosts_raw ingestion parquet mapping \"AmortizedCosts_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\r\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\r\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\r\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\r\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\r\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\r\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\r\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\r\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\r\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\r\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\r\n]\r\n```\r\n\r\n// AmortizedCosts_raw retention policy (clear historical data)\r\n.alter-merge table AmortizedCosts_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// AmortizedCosts_raw retention policy (set the user-defined retention period)\r\n.alter-merge table AmortizedCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable AmortizedCosts_raw streaming ingestion (required for Fabric)\r\n.alter table AmortizedCosts_raw policy streamingingestion disable\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_raw table -- Create the table if it doesn't exist\r\n.create-merge table CommitmentDiscountUsage_raw ( ignore: string )\r\n\r\n// CommitmentDiscountUsage_raw table -- Remove all columns to allow changing column types\r\n.alter table CommitmentDiscountUsage_raw ( ignore: string )\r\n\r\n// CommitmentDiscountUsage_raw table -- Redefine all columns\r\n.alter table CommitmentDiscountUsage_raw (\r\n InstanceFlexibilityGroup: string,\r\n InstanceFlexibilityRatio: real,\r\n InstanceId: string,\r\n Kind: string,\r\n ReservationId: string,\r\n ReservationOrderId: string,\r\n ReservedHours: real,\r\n SkuName: string,\r\n TotalReservedQuantity: real,\r\n UsageDate: datetime,\r\n UsedHours: real,\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// CommitmentDiscountUsage_raw ingestion mapping\r\n.create-or-alter table CommitmentDiscountUsage_raw ingestion parquet mapping \"CommitmentDiscountUsage_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\r\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\r\n { \"Column\": \"InstanceId\", \"Properties\": { \"Field\": \"InstanceId\" } },\r\n { \"Column\": \"Kind\", \"Properties\": { \"Field\": \"Kind\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\r\n { \"Column\": \"ReservedHours\", \"Properties\": { \"Field\": \"ReservedHours\" } },\r\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\r\n { \"Column\": \"TotalReservedQuantity\", \"Properties\": { \"Field\": \"TotalReservedQuantity\" } },\r\n { \"Column\": \"UsageDate\", \"Properties\": { \"Field\": \"UsageDate\" } },\r\n { \"Column\": \"UsedHours\", \"Properties\": { \"Field\": \"UsedHours\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// CommitmentDiscountUsage_raw retention policy (clear historical data)\r\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// CommitmentDiscountUsage_raw retention policy (set the user-defined retention period)\r\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable CommitmentDiscountUsage_raw streaming ingestion (required for Fabric)\r\n.alter table CommitmentDiscountUsage_raw policy streamingingestion disable\r\n\r\n\r\n//===| Costs |==========================================================================================================\r\n// Supported versions:\r\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n// - Tencent: 1.0 -- See https://www.tencentcloud.com/document/product/555/67495 / https://www.tencentcloud.com/document/product/555/67496\r\n// - Alibaba: 1.0 -- See https://www.alibabacloud.com/help/en/user-center/user-guide/export-alibaba-cloud-standard-billing-focus\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_raw table -- Create the table if it doesn't exist\r\n.create-merge table Costs_raw ( ignore: string )\r\n\r\n// Costs_raw table -- Remove all columns to allow changing column types\r\n.alter table Costs_raw ( ignore: string )\r\n\r\n// Costs_raw table -- Redefine all columns\r\n.alter table Costs_raw (\r\n AvailabilityZone: string, // FOCUS 0.5+\r\n BilledCost: real, // FOCUS 0.5+\r\n BillingAccountId: string, // FOCUS 0.5+\r\n BillingAccountName: string, // FOCUS 0.5+\r\n BillingAccountType: string, // Azure 1.0-preview(v1)+\r\n BillingCurrency: string, // FOCUS 0.5+\r\n BillingPeriodEnd: datetime, // FOCUS 0.5+\r\n BillingPeriodStart: datetime, // FOCUS 0.5+\r\n CapacityReservationId: string, // FOCUS 1.1+\r\n CapacityReservationStatus: string, // FOCUS 1.1+\r\n ChargeCategory: string, // FOCUS 1.0-preview+\r\n ChargeClass: string, // FOCUS 1.0+\r\n ChargeDescription: string, // FOCUS 1.0+\r\n ChargeFrequency: string, // FOCUS 1.0+\r\n ChargePeriodEnd: datetime, // FOCUS 0.5+\r\n ChargePeriodStart: datetime, // FOCUS 0.5+\r\n ChargeSubcategory: string, // FOCUS 1.0-preview only\r\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountId: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountName: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountQuantity: real, // FOCUS 1.1+\r\n CommitmentDiscountStatus: string, // FOCUS 1.0+\r\n CommitmentDiscountType: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountUnit: string, // FOCUS 1.1+\r\n ConsumedQuantity: real, // FOCUS 1.0+\r\n ConsumedUnit: string, // FOCUS 1.0+\r\n ContractedCost: real, // FOCUS 1.0+\r\n ContractedUnitPrice: real, // FOCUS 1.0+\r\n EffectiveCost: real, // FOCUS 1.0-preview+\r\n InvoiceId: string, // FOCUS 1.2+\r\n InvoiceIssuerName: string, // FOCUS 0.5+\r\n ListCost: real, // FOCUS 1.0-preview+\r\n ListUnitPrice: real, // FOCUS 1.0-preview+\r\n PricingCategory: string, // FOCUS 1.0-preview+\r\n PricingCurrency: string, // FOCUS 1.2+\r\n PricingQuantity: real, // FOCUS 1.0-preview+\r\n PricingUnit: string, // FOCUS 1.0-preview+\r\n ProviderName: string, // FOCUS 0.5+\r\n PublisherName: string, // FOCUS 0.5+\r\n Region: string, // FOCUS 0.5-1.0-preview (deprecated)\r\n RegionId: string, // FOCUS 1.0+\r\n RegionName: string, // FOCUS 1.0+\r\n ResourceId: string, // FOCUS 0.5+\r\n ResourceName: string, // FOCUS 0.5+\r\n ResourceType: string, // FOCUS 1.0-preview+\r\n ServiceCategory: string, // FOCUS 0.5+\r\n ServiceName: string, // FOCUS 0.5+\r\n ServiceSubcategory: string, // FOCUS 1.1+\r\n SkuId: string, // FOCUS 1.0-preview+\r\n SkuMeter: string, // FOCUS 1.1+\r\n SkuPriceDetails: string, // FOCUS 1.1+\r\n SkuPriceId: string, // FOCUS 1.0-preview+\r\n SubAccountId: string, // FOCUS 0.5+\r\n SubAccountName: string, // FOCUS 0.5+\r\n SubAccountType: string, // Azure 1.0-preview(v1)+\r\n Tags: string, // FOCUS 1.0-preview+\r\n UsageAmount: real, // GCP Jan 2024 -- Removed Mar 2024 (UsageQuantity)\r\n UsageQuantity: real, // FOCUS 1.0-preview only\r\n UsageUnit: string, // FOCUS 1.0-preview only\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_AmortizationClass: string, // Azure 1.2-preview+\r\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingItemCode: string, // Alibaba 1.0+\r\n x_BillingItemName: string, // Alibaba 1.0+\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_CommodityCode: string, // Alibaba 1.0+\r\n x_CommodityName: string, // Alibaba 1.0+\r\n x_ComponentName: string, // Tencent 1.0+\r\n x_ComponentType: string, // Tencent 1.0+\r\n x_ContractedCostInUsd: real, // Azure 1.0+\r\n x_Cost: real, // GCP Jan 2024 -- Removed Jun 2024 (ContractedCost)\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: string, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_CostType: string, // GCP Jan 2024\r\n x_Credits: string, // GCP Jan 2024\r\n x_CurrencyConversionRate: real, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: string, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0+\r\n x_InstanceID: string, // Alibaba 1.0+\r\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_OnDemandCost: real, // Azure 1.0-preview(v1) only\r\n x_OnDemandCostInUsd: real, // Azure 1.0-preview(v1) only\r\n x_OnDemandUnitPrice: real, // Azure 1.0-preview(v1) only\r\n x_Operation: string, // AWS 1.0\r\n x_OwnerAccountID: string, // Tencent 1.0+\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency: string, // Azure 1.0-preview(v1)-1.0r2\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServiceModel: string, // Azure 1.2-preview+\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: string, // Azure 1.0-preview(v1)-1.2-preview\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName: string, // Azure 1.0-preview(v1)-1.0r2\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuPlanName: string, // Azure 1.2-preview+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string, // Hubs v1_0+\r\n x_SubproductName: string, // Tencent 1.0+ // cSpell:ignore Subproduct\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Costs_raw ingestion mapping\r\n.create-or-alter table Costs_raw ingestion parquet mapping \"Costs_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BilledCost\", \"Properties\": { \"Field\": \"BilledCost\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingAccountType\", \"Properties\": { \"Field\": \"BillingAccountType\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEnd\", \"Properties\": { \"Field\": \"BillingPeriodEnd\" } },\r\n { \"Column\": \"BillingPeriodStart\", \"Properties\": { \"Field\": \"BillingPeriodStart\" } },\r\n { \"Column\": \"CapacityReservationId\", \"Properties\": { \"Field\": \"CapacityReservationId\" } },\r\n { \"Column\": \"CapacityReservationStatus\", \"Properties\": { \"Field\": \"CapacityReservationStatus\" } },\r\n { \"Column\": \"ChargeCategory\", \"Properties\": { \"Field\": \"ChargeCategory\" } },\r\n { \"Column\": \"ChargeClass\", \"Properties\": { \"Field\": \"ChargeClass\" } },\r\n { \"Column\": \"ChargeDescription\", \"Properties\": { \"Field\": \"ChargeDescription\" } },\r\n { \"Column\": \"ChargeFrequency\", \"Properties\": { \"Field\": \"ChargeFrequency\" } },\r\n { \"Column\": \"ChargePeriodEnd\", \"Properties\": { \"Field\": \"ChargePeriodEnd\" } },\r\n { \"Column\": \"ChargePeriodStart\", \"Properties\": { \"Field\": \"ChargePeriodStart\" } },\r\n { \"Column\": \"ChargeSubcategory\", \"Properties\": { \"Field\": \"ChargeSubcategory\" } },\r\n { \"Column\": \"CommitmentDiscountCategory\", \"Properties\": { \"Field\": \"CommitmentDiscountCategory\" } },\r\n { \"Column\": \"CommitmentDiscountId\", \"Properties\": { \"Field\": \"CommitmentDiscountId\" } },\r\n { \"Column\": \"CommitmentDiscountName\", \"Properties\": { \"Field\": \"CommitmentDiscountName\" } },\r\n { \"Column\": \"CommitmentDiscountQuantity\", \"Properties\": { \"Field\": \"CommitmentDiscountQuantity\" } },\r\n { \"Column\": \"CommitmentDiscountStatus\", \"Properties\": { \"Field\": \"CommitmentDiscountStatus\" } },\r\n { \"Column\": \"CommitmentDiscountType\", \"Properties\": { \"Field\": \"CommitmentDiscountType\" } },\r\n { \"Column\": \"CommitmentDiscountUnit\", \"Properties\": { \"Field\": \"CommitmentDiscountUnit\" } },\r\n { \"Column\": \"ConsumedQuantity\", \"Properties\": { \"Field\": \"ConsumedQuantity\" } },\r\n { \"Column\": \"ConsumedUnit\", \"Properties\": { \"Field\": \"ConsumedUnit\" } },\r\n { \"Column\": \"ContractedCost\", \"Properties\": { \"Field\": \"ContractedCost\" } },\r\n { \"Column\": \"ContractedUnitPrice\", \"Properties\": { \"Field\": \"ContractedUnitPrice\" } },\r\n { \"Column\": \"EffectiveCost\", \"Properties\": { \"Field\": \"EffectiveCost\" } },\r\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\r\n { \"Column\": \"InvoiceIssuerName\", \"Properties\": { \"Field\": \"InvoiceIssuerName\" } },\r\n { \"Column\": \"ListCost\", \"Properties\": { \"Field\": \"ListCost\" } },\r\n { \"Column\": \"ListUnitPrice\", \"Properties\": { \"Field\": \"ListUnitPrice\" } },\r\n { \"Column\": \"PricingCategory\", \"Properties\": { \"Field\": \"PricingCategory\" } },\r\n { \"Column\": \"PricingCurrency\", \"Properties\": { \"Field\": \"PricingCurrency\" } },\r\n { \"Column\": \"PricingQuantity\", \"Properties\": { \"Field\": \"PricingQuantity\" } },\r\n { \"Column\": \"PricingUnit\", \"Properties\": { \"Field\": \"PricingUnit\" } },\r\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\r\n { \"Column\": \"RegionId\", \"Properties\": { \"Field\": \"RegionId\" } },\r\n { \"Column\": \"RegionName\", \"Properties\": { \"Field\": \"RegionName\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\r\n { \"Column\": \"ServiceCategory\", \"Properties\": { \"Field\": \"ServiceCategory\" } },\r\n { \"Column\": \"ServiceName\", \"Properties\": { \"Field\": \"ServiceName\" } },\r\n { \"Column\": \"ServiceSubcategory\", \"Properties\": { \"Field\": \"ServiceSubcategory\" } },\r\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\r\n { \"Column\": \"SkuMeter\", \"Properties\": { \"Field\": \"SkuMeter\" } },\r\n { \"Column\": \"SkuPriceDetails\", \"Properties\": { \"Field\": \"SkuPriceDetails\" } },\r\n { \"Column\": \"SkuPriceId\", \"Properties\": { \"Field\": \"SkuPriceId\" } },\r\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\r\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\r\n { \"Column\": \"SubAccountType\", \"Properties\": { \"Field\": \"SubAccountType\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"UsageAmount\", \"Properties\": { \"Field\": \"UsageAmount\" } },\r\n { \"Column\": \"UsageQuantity\", \"Properties\": { \"Field\": \"UsageQuantity\" } },\r\n { \"Column\": \"UsageUnit\", \"Properties\": { \"Field\": \"UsageUnit\" } },\r\n { \"Column\": \"x_AccountId\", \"Properties\": { \"Field\": \"x_AccountId\" } },\r\n { \"Column\": \"x_AccountName\", \"Properties\": { \"Field\": \"x_AccountName\" } },\r\n { \"Column\": \"x_AccountOwnerId\", \"Properties\": { \"Field\": \"x_AccountOwnerId\" } },\r\n { \"Column\": \"x_AmortizationClass\", \"Properties\": { \"Field\": \"x_AmortizationClass\" } },\r\n { \"Column\": \"x_BilledCostInUsd\", \"Properties\": { \"Field\": \"x_BilledCostInUsd\" } },\r\n { \"Column\": \"x_BilledUnitPrice\", \"Properties\": { \"Field\": \"x_BilledUnitPrice\" } },\r\n { \"Column\": \"x_BillingAccountId\", \"Properties\": { \"Field\": \"x_BillingAccountId\" } },\r\n { \"Column\": \"x_BillingAccountName\", \"Properties\": { \"Field\": \"x_BillingAccountName\" } },\r\n { \"Column\": \"x_BillingExchangeRate\", \"Properties\": { \"Field\": \"x_BillingExchangeRate\" } },\r\n { \"Column\": \"x_BillingExchangeRateDate\", \"Properties\": { \"Field\": \"x_BillingExchangeRateDate\" } },\r\n { \"Column\": \"x_BillingItemCode\", \"Properties\": { \"Field\": \"x_BillingItemCode\" } },\r\n { \"Column\": \"x_BillingItemName\", \"Properties\": { \"Field\": \"x_BillingItemName\" } },\r\n { \"Column\": \"x_BillingProfileId\", \"Properties\": { \"Field\": \"x_BillingProfileId\" } },\r\n { \"Column\": \"x_BillingProfileName\", \"Properties\": { \"Field\": \"x_BillingProfileName\" } },\r\n { \"Column\": \"x_ChargeId\", \"Properties\": { \"Field\": \"x_ChargeId\" } },\r\n { \"Column\": \"x_ContractedCostInUsd\", \"Properties\": { \"Field\": \"x_ContractedCostInUsd\" } },\r\n { \"Column\": \"x_CommodityCode\", \"Properties\": { \"Field\": \"x_CommodityCode\" } },\r\n { \"Column\": \"x_CommodityName\", \"Properties\": { \"Field\": \"x_CommodityName\" } },\r\n { \"Column\": \"x_ComponentName\", \"Properties\": { \"Field\": \"x_ComponentName\" } },\r\n { \"Column\": \"x_ComponentType\", \"Properties\": { \"Field\": \"x_ComponentType\" } },\r\n { \"Column\": \"x_Cost\", \"Properties\": { \"Field\": \"x_Cost\" } },\r\n { \"Column\": \"x_CostAllocationRuleName\", \"Properties\": { \"Field\": \"x_CostAllocationRuleName\" } },\r\n { \"Column\": \"x_CostCategories\", \"Properties\": { \"Field\": \"x_CostCategories\" } },\r\n { \"Column\": \"x_CostCenter\", \"Properties\": { \"Field\": \"x_CostCenter\" } },\r\n { \"Column\": \"x_Credits\", \"Properties\": { \"Field\": \"x_Credits\" } },\r\n { \"Column\": \"x_CostType\", \"Properties\": { \"Field\": \"x_CostType\" } },\r\n { \"Column\": \"x_CurrencyConversionRate\", \"Properties\": { \"Field\": \"x_CurrencyConversionRate\" } },\r\n { \"Column\": \"x_CustomerId\", \"Properties\": { \"Field\": \"x_CustomerId\" } },\r\n { \"Column\": \"x_CustomerName\", \"Properties\": { \"Field\": \"x_CustomerName\" } },\r\n { \"Column\": \"x_Discount\", \"Properties\": { \"Field\": \"x_Discount\" } },\r\n { \"Column\": \"x_EffectiveCostInUsd\", \"Properties\": { \"Field\": \"x_EffectiveCostInUsd\" } },\r\n { \"Column\": \"x_EffectiveUnitPrice\", \"Properties\": { \"Field\": \"x_EffectiveUnitPrice\" } },\r\n { \"Column\": \"x_ExportTime\", \"Properties\": { \"Field\": \"x_ExportTime\" } },\r\n { \"Column\": \"x_InstanceID\", \"Properties\": { \"Field\": \"x_InstanceID\" } },\r\n { \"Column\": \"x_InvoiceId\", \"Properties\": { \"Field\": \"x_InvoiceId\" } },\r\n { \"Column\": \"x_InvoiceIssuerId\", \"Properties\": { \"Field\": \"x_InvoiceIssuerId\" } },\r\n { \"Column\": \"x_InvoiceSectionId\", \"Properties\": { \"Field\": \"x_InvoiceSectionId\" } },\r\n { \"Column\": \"x_InvoiceSectionName\", \"Properties\": { \"Field\": \"x_InvoiceSectionName\" } },\r\n { \"Column\": \"x_ListCostInUsd\", \"Properties\": { \"Field\": \"x_ListCostInUsd\" } },\r\n { \"Column\": \"x_Location\", \"Properties\": { \"Field\": \"x_Location\" } },\r\n { \"Column\": \"x_OnDemandCost\", \"Properties\": { \"Field\": \"x_OnDemandCost\" } },\r\n { \"Column\": \"x_OnDemandCostInUsd\", \"Properties\": { \"Field\": \"x_OnDemandCostInUsd\" } },\r\n { \"Column\": \"x_OnDemandUnitPrice\", \"Properties\": { \"Field\": \"x_OnDemandUnitPrice\" } },\r\n { \"Column\": \"x_Operation\", \"Properties\": { \"Field\": \"x_Operation\" } },\r\n { \"Column\": \"x_OwnerAccountID\", \"Properties\": { \"Field\": \"x_OwnerAccountID\" } },\r\n { \"Column\": \"x_PartnerCreditApplied\", \"Properties\": { \"Field\": \"x_PartnerCreditApplied\" } },\r\n { \"Column\": \"x_PartnerCreditRate\", \"Properties\": { \"Field\": \"x_PartnerCreditRate\" } },\r\n { \"Column\": \"x_PricingBlockSize\", \"Properties\": { \"Field\": \"x_PricingBlockSize\" } },\r\n { \"Column\": \"x_PricingCurrency\", \"Properties\": { \"Field\": \"x_PricingCurrency\" } },\r\n { \"Column\": \"x_PricingSubcategory\", \"Properties\": { \"Field\": \"x_PricingSubcategory\" } },\r\n { \"Column\": \"x_PricingUnitDescription\", \"Properties\": { \"Field\": \"x_PricingUnitDescription\" } },\r\n { \"Column\": \"x_Project\", \"Properties\": { \"Field\": \"x_Project\" } },\r\n { \"Column\": \"x_PublisherCategory\", \"Properties\": { \"Field\": \"x_PublisherCategory\" } },\r\n { \"Column\": \"x_PublisherId\", \"Properties\": { \"Field\": \"x_PublisherId\" } },\r\n { \"Column\": \"x_ResellerId\", \"Properties\": { \"Field\": \"x_ResellerId\" } },\r\n { \"Column\": \"x_ResellerName\", \"Properties\": { \"Field\": \"x_ResellerName\" } },\r\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\r\n { \"Column\": \"x_ResourceType\", \"Properties\": { \"Field\": \"x_ResourceType\" } },\r\n { \"Column\": \"x_ServiceCode\", \"Properties\": { \"Field\": \"x_ServiceCode\" } },\r\n { \"Column\": \"x_ServiceId\", \"Properties\": { \"Field\": \"x_ServiceId\" } },\r\n { \"Column\": \"x_ServiceModel\", \"Properties\": { \"Field\": \"x_ServiceModel\" } },\r\n { \"Column\": \"x_ServicePeriodEnd\", \"Properties\": { \"Field\": \"x_ServicePeriodEnd\" } },\r\n { \"Column\": \"x_ServicePeriodStart\", \"Properties\": { \"Field\": \"x_ServicePeriodStart\" } },\r\n { \"Column\": \"x_SkuDescription\", \"Properties\": { \"Field\": \"x_SkuDescription\" } },\r\n { \"Column\": \"x_SkuDetails\", \"Properties\": { \"Field\": \"x_SkuDetails\" } },\r\n { \"Column\": \"x_SkuIsCreditEligible\", \"Properties\": { \"Field\": \"x_SkuIsCreditEligible\" } },\r\n { \"Column\": \"x_SkuMeterCategory\", \"Properties\": { \"Field\": \"x_SkuMeterCategory\" } },\r\n { \"Column\": \"x_SkuMeterId\", \"Properties\": { \"Field\": \"x_SkuMeterId\" } },\r\n { \"Column\": \"x_SkuMeterName\", \"Properties\": { \"Field\": \"x_SkuMeterName\" } },\r\n { \"Column\": \"x_SkuMeterSubcategory\", \"Properties\": { \"Field\": \"x_SkuMeterSubcategory\" } },\r\n { \"Column\": \"x_SkuOfferId\", \"Properties\": { \"Field\": \"x_SkuOfferId\" } },\r\n { \"Column\": \"x_SkuOrderId\", \"Properties\": { \"Field\": \"x_SkuOrderId\" } },\r\n { \"Column\": \"x_SkuOrderName\", \"Properties\": { \"Field\": \"x_SkuOrderName\" } },\r\n { \"Column\": \"x_SkuPartNumber\", \"Properties\": { \"Field\": \"x_SkuPartNumber\" } },\r\n { \"Column\": \"x_SkuPlanName\", \"Properties\": { \"Field\": \"x_SkuPlanName\" } },\r\n { \"Column\": \"x_SkuRegion\", \"Properties\": { \"Field\": \"x_SkuRegion\" } },\r\n { \"Column\": \"x_SkuServiceFamily\", \"Properties\": { \"Field\": \"x_SkuServiceFamily\" } },\r\n { \"Column\": \"x_SkuTerm\", \"Properties\": { \"Field\": \"x_SkuTerm\" } },\r\n { \"Column\": \"x_SkuTier\", \"Properties\": { \"Field\": \"x_SkuTier\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } },\r\n { \"Column\": \"x_SubproductName\", \"Properties\": { \"Field\": \"x_SubproductName\" } },\r\n { \"Column\": \"x_UsageType\", \"Properties\": { \"Field\": \"x_UsageType\" } }\r\n]\r\n```\r\n\r\n// Costs_raw retention policy (clear historical data)\r\n.alter-merge table Costs_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Costs_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Costs_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Costs_raw streaming ingestion (required for Fabric)\r\n.alter table Costs_raw policy streamingingestion disable\r\n\r\n\r\n//===| Prices |=========================================================================================================\r\n// NOTE: Must be before cost details.\r\n//\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_raw table -- Create the table if it doesn't exist\r\n.create-merge table Prices_raw ( ignore: string )\r\n\r\n// Prices_raw table -- Remove all columns to allow changing column types\r\n.alter table Prices_raw ( ignore: string )\r\n\r\n// Prices_raw table -- Redefine all columns\r\n.alter table Prices_raw (\r\n BasePrice: real, // Azure EA + MCA\r\n BillingAccountId: string, // Azure MCA\r\n BillingAccountName: string, // Azure MCA\r\n BillingCurrency: string, // Azure MCA\r\n BillingProfileId: string, // Azure MCA\r\n BillingProfileName: string, // Azure MCA\r\n Currency: string, // Azure MCA\r\n CurrencyCode: string, // Azure EA\r\n EffectiveEndDate: datetime, // Azure MCA\r\n EffectiveStartDate: datetime, // Azure EA + MCA\r\n EnrollmentNumber: string, // Azure EA\r\n IncludedQuantity: real, // Azure EA\r\n MarketPrice: real, // Azure EA + MCA\r\n MeterCategory: string, // Azure EA + MCA\r\n MeterId: string, // Azure MCA\r\n MeterID: string, // Azure EA\r\n MeterName: string, // Azure EA + MCA\r\n MeterRegion: string, // Azure EA + MCA\r\n MeterSubCategory: string, // Azure EA + MCA\r\n MeterType: string, // Azure EA + MCA\r\n OfferID: string, // Azure EA\r\n PartNumber: string, // Azure EA\r\n PriceType: string, // Azure EA + MCA\r\n Product: string, // Azure EA + MCA\r\n ProductId: string, // Azure MCA\r\n ProductID: string, // Azure EA\r\n ServiceFamily: string, // Azure EA + MCA\r\n SkuId: string, // Azure MCA\r\n SkuID: string, // Azure EA\r\n Term: string, // Azure EA + MCA\r\n TierMinimumUnits: real, // Azure MCA\r\n UnitOfMeasure: string, // Azure EA + MCA\r\n UnitPrice: real, // Azure EA + MCA\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Prices_raw ingestion mapping\r\n.create-or-alter table Prices_raw ingestion parquet mapping \"Prices_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"BasePrice\", \"Properties\": { \"Field\": \"BasePrice\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\r\n { \"Column\": \"CurrencyCode\", \"Properties\": { \"Field\": \"CurrencyCode\" } },\r\n { \"Column\": \"EffectiveEndDate\", \"Properties\": { \"Field\": \"EffectiveEndDate\" } },\r\n { \"Column\": \"EffectiveStartDate\", \"Properties\": { \"Field\": \"EffectiveStartDate\" } },\r\n { \"Column\": \"EnrollmentNumber\", \"Properties\": { \"Field\": \"EnrollmentNumber\" } },\r\n { \"Column\": \"IncludedQuantity\", \"Properties\": { \"Field\": \"IncludedQuantity\" } },\r\n { \"Column\": \"MarketPrice\", \"Properties\": { \"Field\": \"MarketPrice\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterID\", \"Properties\": { \"Field\": \"MeterID\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"MeterType\", \"Properties\": { \"Field\": \"MeterType\" } },\r\n { \"Column\": \"OfferID\", \"Properties\": { \"Field\": \"OfferID\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PriceType\", \"Properties\": { \"Field\": \"PriceType\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductId\", \"Properties\": { \"Field\": \"ProductId\" } },\r\n { \"Column\": \"ProductID\", \"Properties\": { \"Field\": \"ProductID\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\r\n { \"Column\": \"SkuID\", \"Properties\": { \"Field\": \"SkuID\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"TierMinimumUnits\", \"Properties\": { \"Field\": \"TierMinimumUnits\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Prices_raw retention policy (clear historical data)\r\n.alter-merge table Prices_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Prices_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Prices_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Prices_raw streaming ingestion (required for Fabric)\r\n.alter table Prices_raw policy streamingingestion disable\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_raw table -- Create the table if it doesn't exist\r\n.create-merge table Recommendations_raw ( ignore: string )\r\n\r\n// Recommendations_raw table -- Remove all columns to allow changing column types\r\n.alter table Recommendations_raw ( ignore: string )\r\n\r\n// Recommendations_raw table -- Redefine all columns\r\n.alter table Recommendations_raw (\r\n CostWithNoReservedInstances: real, // MS CM EA resv reco 2024-05-01\r\n CostWithNoReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n FirstUsageDate: datetime, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n InstanceFlexibilityGroup: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n InstanceFlexibilityRatio: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n Location: string, // MS CM EA+MCA resv reco 2024-05-01\r\n LookBackPeriod: string, // MS CM EA+MCA resv reco 2024-05-01\r\n MeterId: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n NetSavings: real, // MS CM EA resv reco 2024-05-01\r\n NetSavingsJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n NormalizedSize: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n ProviderName: string, // Hubs v1_2\r\n RecommendedQuantity: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n RecommendedQuantityNormalized: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n ResourceId: string, // Hubs v1_2\r\n ResourceName: string, // Hubs v1_2\r\n ResourceType: string, // Hubs v1_2, MS CM EA+MCA resv reco 2024-05-01\r\n Scope: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n SKU: string, // MS CM EA resv reco 2024-05-01\r\n SkuName: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces\r\n SkuProperties: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n SubAccountId: string, // Hubs v1_2\r\n SubAccountName: string, // Hubs v1_2\r\n SubscriptionId: string, // MS CM EA+MCA resv reco 2024-05-01\r\n Term: string, // MS CM EA+MCA resv reco 2024-05-01\r\n TotalCostWithReservedInstances: real, // MS CM EA resv reco 2024-05-01\r\n TotalCostWithReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n x_EffectiveCostAfter: real, // Hubs v1_2\r\n x_EffectiveCostBefore: real, // Hubs v1_2\r\n x_EffectiveCostSavings: real, // Hubs v1_2\r\n x_RecommendationCategory: string, // Hubs v1_2\r\n x_RecommendationDate: datetime, // Hubs v1_2\r\n x_RecommendationDescription: string, // Hubs v1_2\r\n x_RecommendationDetails: dynamic, // Hubs v1_2\r\n x_RecommendationId: string, // Hubs v1_2\r\n x_ResourceGroupName: string, // Hubs v1_2\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Recommendations_raw ingestion mapping\r\n.create-or-alter table Recommendations_raw ingestion parquet mapping \"Recommendations_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"CostWithNoReservedInstances\", \"Properties\": { \"Field\": \"CostWithNoReservedInstances\" } },\r\n { \"Column\": \"CostWithNoReservedInstancesJson\", \"Properties\": { \"Field\": \"CostWithNoReservedInstancesJson\" } },\r\n { \"Column\": \"FirstUsageDate\", \"Properties\": { \"Field\": \"FirstUsageDate\" } },\r\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\r\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\r\n { \"Column\": \"Location\", \"Properties\": { \"Field\": \"Location\" } },\r\n { \"Column\": \"LookBackPeriod\", \"Properties\": { \"Field\": \"LookBackPeriod\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"NetSavings\", \"Properties\": { \"Field\": \"NetSavings\" } },\r\n { \"Column\": \"NetSavingsJson\", \"Properties\": { \"Field\": \"NetSavingsJson\" } },\r\n { \"Column\": \"NormalizedSize\", \"Properties\": { \"Field\": \"NormalizedSize\" } },\r\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\r\n { \"Column\": \"RecommendedQuantity\", \"Properties\": { \"Field\": \"RecommendedQuantity\" } },\r\n { \"Column\": \"RecommendedQuantityNormalized\", \"Properties\": { \"Field\": \"RecommendedQuantityNormalized\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\r\n { \"Column\": \"Scope\", \"Properties\": { \"Field\": \"Scope\" } },\r\n { \"Column\": \"SKU\", \"Properties\": { \"Field\": \"SKU\" } },\r\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\r\n { \"Column\": \"SkuProperties\", \"Properties\": { \"Field\": \"SkuProperties\" } },\r\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\r\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"TotalCostWithReservedInstances\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstances\" } },\r\n { \"Column\": \"TotalCostWithReservedInstancesJson\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstancesJson\" } },\r\n { \"Column\": \"x_EffectiveCostAfter\", \"Properties\": { \"Field\": \"x_EffectiveCostAfter\" } },\r\n { \"Column\": \"x_EffectiveCostBefore\", \"Properties\": { \"Field\": \"x_EffectiveCostBefore\" } },\r\n { \"Column\": \"x_EffectiveCostSavings\", \"Properties\": { \"Field\": \"x_EffectiveCostSavings\" } },\r\n { \"Column\": \"x_RecommendationCategory\", \"Properties\": { \"Field\": \"x_RecommendationCategory\" } },\r\n { \"Column\": \"x_RecommendationDate\", \"Properties\": { \"Field\": \"x_RecommendationDate\" } },\r\n { \"Column\": \"x_RecommendationDescription\", \"Properties\": { \"Field\": \"x_RecommendationDescription\" } },\r\n { \"Column\": \"x_RecommendationDetails\", \"Properties\": { \"Field\": \"x_RecommendationDetails\" } },\r\n { \"Column\": \"x_RecommendationId\", \"Properties\": { \"Field\": \"x_RecommendationId\" } },\r\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Recommendations_raw retention policy (clear historical data)\r\n.alter-merge table Recommendations_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Recommendations_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Recommendations_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Recommendations_raw streaming ingestion (required for Fabric)\r\n.alter table Recommendations_raw policy streamingingestion disable\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_raw table -- Create the table if it doesn't exist\r\n.create-merge table Transactions_raw ( ignore: string )\r\n\r\n// Transactions_raw table -- Remove all columns to allow changing column types\r\n.alter table Transactions_raw ( ignore: string )\r\n\r\n// Transactions_raw table -- Redefine all columns\r\n.alter table Transactions_raw (\r\n AccountName: string, // MS CM EA resv trans 2023-05-01\r\n AccountOwnerEmail: string, // MS CM EA resv trans 2023-05-01\r\n Amount: real, // MS CM EA+MCA resv trans 2023-05-01\r\n ArmSkuName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n BillingFrequency: string, // MS CM EA+MCA resv trans 2023-05-01\r\n BillingMonth: string, // MS CM EA resv trans 2023-05-01\r\n BillingProfileId: string, // MS CM MCA resv trans 2023-05-01\r\n BillingProfileName: string, // MS CM MCA resv trans 2023-05-01\r\n CostCenter: string, // MS CM EA resv trans 2023-05-01\r\n Currency: string, // MS CM EA+MCA resv trans 2023-05-01\r\n CurrentEnrollmentId: string, // MS CM EA resv trans 2023-05-01\r\n DepartmentName: string, // MS CM EA resv trans 2023-05-01\r\n Description: string, // MS CM EA+MCA resv trans 2023-05-01\r\n EventDate: datetime, // MS CM EA+MCA resv trans 2023-05-01\r\n EventType: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Invoice: string, // MS CM EA+MCA resv trans 2023-05-01\r\n InvoiceId: string, // MS CM EA+MCA resv trans 2023-05-01\r\n InvoiceSectionId: string, // MS CM MCA resv trans 2023-05-01\r\n InvoiceSectionName: string, // MS CM MCA resv trans 2023-05-01\r\n MonetaryCommitment: real, // MS CM EA resv trans 2023-05-01\r\n Overage: real, // MS CM EA resv trans 2023-05-01\r\n PurchasingEnrollment: string, // MS CM EA resv trans 2023-05-01\r\n PurchasingSubscriptionGuid: string, // MS CM EA+MCA resv trans 2023-05-01\r\n PurchasingSubscriptionName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Quantity: real, // MS CM EA+MCA resv trans 2023-05-01\r\n Region: string, // MS CM EA+MCA resv trans 2023-05-01\r\n ReservationOrderId: string, // MS CM EA+MCA resv trans 2023-05-01\r\n ReservationOrderName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Term: string, // MS CM EA+MCA resv trans 2023-05-01\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Transactions_raw ingestion mapping\r\n.create-or-alter table Transactions_raw ingestion parquet mapping \"Transactions_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerEmail\", \"Properties\": { \"Field\": \"AccountOwnerEmail\" } },\r\n { \"Column\": \"Amount\", \"Properties\": { \"Field\": \"Amount\" } },\r\n { \"Column\": \"ArmSkuName\", \"Properties\": { \"Field\": \"ArmSkuName\" } },\r\n { \"Column\": \"BillingFrequency\", \"Properties\": { \"Field\": \"BillingFrequency\" } },\r\n { \"Column\": \"BillingMonth\", \"Properties\": { \"Field\": \"BillingMonth\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\r\n { \"Column\": \"CurrentEnrollmentId\", \"Properties\": { \"Field\": \"CurrentEnrollmentId\" } },\r\n { \"Column\": \"DepartmentName\", \"Properties\": { \"Field\": \"DepartmentName\" } },\r\n { \"Column\": \"Description\", \"Properties\": { \"Field\": \"Description\" } },\r\n { \"Column\": \"EventDate\", \"Properties\": { \"Field\": \"EventDate\" } },\r\n { \"Column\": \"EventType\", \"Properties\": { \"Field\": \"EventType\" } },\r\n { \"Column\": \"Invoice\", \"Properties\": { \"Field\": \"Invoice\" } },\r\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"InvoiceSectionName\", \"Properties\": { \"Field\": \"InvoiceSectionName\" } },\r\n { \"Column\": \"MonetaryCommitment\", \"Properties\": { \"Field\": \"MonetaryCommitment\" } },\r\n { \"Column\": \"Overage\", \"Properties\": { \"Field\": \"Overage\" } },\r\n { \"Column\": \"PurchasingEnrollment\", \"Properties\": { \"Field\": \"PurchasingEnrollment\" } },\r\n { \"Column\": \"PurchasingSubscriptionGuid\", \"Properties\": { \"Field\": \"PurchasingSubscriptionGuid\" } },\r\n { \"Column\": \"PurchasingSubscriptionName\", \"Properties\": { \"Field\": \"PurchasingSubscriptionName\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\r\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\r\n { \"Column\": \"ReservationOrderName\", \"Properties\": { \"Field\": \"ReservationOrderName\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Transactions_raw retention policy (clear historical data)\r\n.alter-merge table Transactions_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Transactions_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Transactions_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Transactions_raw streaming ingestion (required for Fabric)\r\n.alter table Transactions_raw policy streamingingestion disable\r\n\r\n", + "$fxv#9": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Prices |=========================================================================================================\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All prices transformed to FOCUS 1.0. Use Prices_transform_v1_2() instead.', folder='Prices')\r\nPrices_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n let prices = materialize(\r\n Prices_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n BasePrice = todecimal(BasePrice),\r\n IncludedQuantity = todecimal(IncludedQuantity),\r\n MarketPrice = todecimal(MarketPrice),\r\n TierMinimumUnits = todecimal(TierMinimumUnits),\r\n UnitPrice = todecimal(UnitPrice)\r\n //\r\n | extend x_SkuId = coalesce(SkuId, SkuID)\r\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\r\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\r\n | extend x_SkuTerm = isoMonths(Term)\r\n | project-rename\r\n x_BaseUnitPrice = BasePrice,\r\n x_EffectivePeriodEnd = EffectiveEndDate,\r\n x_EffectivePeriodStart = EffectiveStartDate,\r\n x_PricingUnitDescription = UnitOfMeasure,\r\n x_SkuIncludedQuantity = IncludedQuantity,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuMeterType = MeterType,\r\n x_SkuOfferId = OfferID,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPriceType = PriceType,\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTier = TierMinimumUnits\r\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, todecimal('')) // UnitPrice for savings plan is not the on-demand unit price\r\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, todecimal('')) // MarketPrice for savings plan is not the list price\r\n | extend ChargeCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Usage',\r\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\r\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\r\n ''\r\n )\r\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\r\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\r\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\r\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\r\n //\r\n // Get latest ingested row based on the unique ID\r\n | extend x_IngestionTime = ingestion_time()\r\n );\r\n //\r\n // Meters for reservations and savings plans to identify commitment eligibility\r\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\r\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\r\n //\r\n // Copy list/base/contracted prices from on-demand SKUs\r\n prices\r\n | where x_SkuPriceType == 'SavingsPlan'\r\n // If we use join, specify the shuffle key\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\r\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\r\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\r\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\r\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\r\n //\r\n // Calculate commitment discount elgibility\r\n // TODO: Would a join be faster?\r\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\r\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\r\n //\r\n // Add PricingUnit and x_PricingBlockSize\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\r\n | lookup kind=leftouter (PricingUnits | extend x_PricingBlockSize = todecimal(x_PricingBlockSize)) on x_PricingUnitDescription\r\n //\r\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, todecimal('')) // Savings plan prices are for the effective price, not the contracted price\r\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\r\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\r\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\r\n | project\r\n BillingAccountId = tolower(case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n BillingAccountId startswith '/', BillingAccountId,\r\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\r\n )),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\r\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\r\n ChargeCategory,\r\n CommitmentDiscountCategory = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Usage',\r\n x_SkuPriceType == 'SavingsPlan', 'Spend',\r\n ''\r\n ),\r\n CommitmentDiscountType = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\r\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\r\n ''\r\n ),\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed',\r\n ''\r\n ),\r\n PricingUnit,\r\n SkuId = coalesce(ProductId, ProductID),\r\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n strlen(x_BillingAccountId) > 32, 'MCA',\r\n strlen(x_BillingAccountId) < 32, 'EA',\r\n 'Unknown'\r\n ),\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\r\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingCurrency = coalesce(Currency, CurrencyCode), // CurrencyCode last as a fallback only\r\n x_PricingSubcategory = case(\r\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_SkuDescription = Product,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\r\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\r\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\r\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\r\n}\r\n\r\n// Prices_final_v1_0 table\r\n.create-merge table Prices_final_v1_0 (\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n ChargeCategory: string,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountType: string,\r\n ContractedUnitPrice: decimal,\r\n ListUnitPrice: decimal,\r\n PricingCategory: string,\r\n PricingUnit: string,\r\n SkuId: string,\r\n SkuPriceId: string,\r\n SkuPriceIdv2: string, // Hubs add-on\r\n x_BaseUnitPrice: decimal, // Azure\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure MCA\r\n x_BillingProfileId: string, // Azure MCA\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_ContractedUnitPriceDiscount: decimal, // Hubs add-on\r\n x_ContractedUnitPriceDiscountPercent: decimal, // Hubs add-on\r\n x_EffectivePeriodEnd: datetime, // Azure\r\n x_EffectivePeriodStart: datetime, // Azure\r\n x_EffectiveUnitPrice: decimal, // Azure\r\n x_EffectiveUnitPriceDiscount: decimal, // Hubs add-on\r\n x_EffectiveUnitPriceDiscountPercent: decimal, // Hubs add-on\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_PricingBlockSize: decimal, // Hubs add-on\r\n x_PricingCurrency: string, // Azure\r\n x_PricingSubcategory: string, // Hubs add-on\r\n x_PricingUnitDescription: string, // Azure\r\n x_SkuDescription: string, // Azure\r\n x_SkuId: string, // Azure\r\n x_SkuIncludedQuantity: decimal, // Azure EA\r\n x_SkuMeterCategory: string, // Azure\r\n x_SkuMeterId: string, // Azure\r\n x_SkuMeterName: string, // Azure\r\n x_SkuMeterSubcategory: string, // Azure\r\n x_SkuMeterType: string, // Azure\r\n x_SkuPriceType: string, // Azure\r\n x_SkuProductId: string, // Azure\r\n x_SkuRegion: string, // Azure\r\n x_SkuServiceFamily: string, // Azure\r\n x_SkuOfferId: string, // Azure EA\r\n x_SkuPartNumber: string, // Azure EA\r\n x_SkuTerm: int, // Azure\r\n x_SkuTier: decimal, // Azure MCA\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_TotalUnitPriceDiscount: decimal, // Hubs add-on\r\n x_TotalUnitPriceDiscountPercent: decimal // Hubs add-on\r\n)\r\n\r\n// Update policy for Prices_raw -> Prices_final_v1_0\r\n.alter table Prices_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Prices_raw\",\r\n \"Query\": \"Prices_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Cost and usage |=================================================================================================\r\n// Supported versions:\r\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All costs transformed to FOCUS 1.0. Use Costs_transform_v1_2() instead.', folder='Costs')\r\nCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n Costs_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n ContractedCost = todecimal(ContractedCost),\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n EffectiveCost = todecimal(EffectiveCost),\r\n ListCost = todecimal(ListCost),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n UsageAmount = todecimal(UsageAmount),\r\n UsageQuantity = todecimal(UsageQuantity),\r\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\r\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\r\n x_Cost = todecimal(x_Cost),\r\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\r\n x_OnDemandCost = todecimal(x_OnDemandCost),\r\n x_OnDemandCostInUsd = todecimal(x_OnDemandCostInUsd),\r\n x_OnDemandUnitPrice = todecimal(x_OnDemandUnitPrice),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\r\n //\r\n // Dedupe rows\r\n | extend x_IngestionTime = ingestion_time()\r\n | extend x_ChargeId = ''\r\n // TODO: Consider adding a unique charge ID per row\r\n // hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // // 1. Resource hierarchy (including resource name), highest to lowest\r\n // BillingAccountId,\r\n // x_InvoiceSectionId,\r\n // x_AccountOwnerId,\r\n // SubAccountId,\r\n // x_ResourceGroupName,\r\n // ResourceName,\r\n // // 2. Resource details\r\n // ResourceId,\r\n // RegionId,\r\n // Tags,\r\n // CommitmentDiscountId,\r\n // x_CostCenter,\r\n // // 4. Meter details\r\n // SkuPriceId,\r\n // x_SkuMeterId,\r\n // x_SkuPartNumber,\r\n // x_SkuOfferId,\r\n // x_SkuDetails,\r\n // // 5. Date\r\n // ChargePeriodStart\r\n // ))\r\n //\r\n // Identify data quality issues\r\n | extend x_SourceChanges = trim_end(',', strcat(\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\r\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\r\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\r\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\r\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\r\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\r\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\r\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\r\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\r\n 'XEffectiveUnitPriceRoundingError,', ''),\r\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\r\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\r\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\r\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\r\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\r\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\r\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\r\n ))\r\n //\r\n // Fix columns needed in other changes\r\n | extend ProviderName = case(\r\n isnotempty(ProviderName), ProviderName,\r\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\r\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\r\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\r\n ''\r\n )\r\n //\r\n // Identify source\r\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\r\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\r\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\r\n ''\r\n ))\r\n // Append version check error code\r\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\r\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\r\n )\r\n //\r\n // Fix quantities\r\n | extend PricingQuantity = case(\r\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\r\n PricingQuantity\r\n )\r\n | extend ConsumedQuantity = case(\r\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\r\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, decimal(1)),\r\n ConsumedQuantity\r\n )\r\n //\r\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\r\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\r\n and (ListUnitPrice == 0 or ContractedUnitPrice == 0)\r\n and x_EffectiveUnitPrice != 0\r\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\r\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\r\n | as allCosts\r\n | where tmp_MissingPrices\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | as costsWithMissingPrices\r\n | join kind=leftouter (\r\n Prices_final_v1_0\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\r\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\r\n ) on tmp_ReservationPriceLookupKey\r\n //\r\n // Select the best price to use for each row\r\n // TODO: Save values before changing -- | extend x_old_ContractedUnitPrice = ContractedUnitPrice, x_old_EffectiveUnitPrice = x_EffectiveUnitPrice, x_old_ListUnitPrice = ListUnitPrice, x_old_ListCost = ListCost, x_old_ContractedCost = ContractedCost\r\n | extend x_EffectiveUnitPrice = case(\r\n // If price is a rounding error away from the billed price, use the billed price\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\r\n // If price is a rounding error away from the contracted price, use the contracted price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ContractedUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\r\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ListUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // Otherwise, assume the contracted price is the same as list price to support aggregations\r\n ContractedUnitPrice\r\n )\r\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\r\n | extend ContractedCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\r\n // ContractedCost is 0 in all other scenarios...\r\n // If 0 and there's a billed cost and prices are the same, use BilledCost\r\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\r\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\r\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume EffectiveCost\r\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ContractedCost\r\n )\r\n | extend ListCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\r\n // ListCost is 0 in all other scenarios...\r\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\r\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume ContractedCost\r\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ListCost\r\n )\r\n // Merge the rest of the unmodified cost records and remove excess columns\r\n | union (allCosts | where not(tmp_MissingPrices))\r\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\r\n //\r\n // BUG: Fix ContractedCost that has bad values\r\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\r\n //\r\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\r\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), todecimal(''))\r\n | extend ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\r\n //\r\n // Convert IDs to lowercase for consistency\r\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\r\n //\r\n // BUG: Remove EffectiveCost for commitment discount purchases\r\n | extend EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), EffectiveCost)\r\n | extend x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), x_EffectiveCostInUsd)\r\n //\r\n // Clean up resource columns\r\n | extend ResourceId = case(\r\n isnotempty(ResourceId), ResourceId,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\r\n ResourceId)\r\n | extend ResourceName = tolower(case(\r\n isnotempty(ResourceName), ResourceName,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\r\n ResourceName))\r\n | extend x_ResourceType = case(\r\n isnotempty(x_ResourceType), x_ResourceType,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\r\n x_ResourceType)\r\n | extend ResourceType = case(\r\n // Use existing resource type display name unless it's an internal resource type ID\r\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\r\n // Use CommitmentDiscountType for commitment discount purchases\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\r\n // Look up display name from internal type\r\n isnotempty(x_ResourceType), coalesce(resource_type(x_ResourceType).SingularDisplayName, ResourceType, x_ResourceType),\r\n ResourceType)\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId = tolower(BillingAccountId),\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEnd),\r\n BillingPeriodStart = startofmonth(BillingPeriodStart),\r\n ChargeCategory = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Credit', 'Credit',\r\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\r\n ChargeCategory\r\n ),\r\n ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass),\r\n ChargeDescription,\r\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\r\n ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency),\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = tolower(CommitmentDiscountId),\r\n CommitmentDiscountName,\r\n CommitmentDiscountStatus = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Used Commitment', 'Used',\r\n ChargeSubcategory == 'Unused Commitment', 'Unused',\r\n CommitmentDiscountStatus\r\n ),\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\r\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\r\n EffectiveCost,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n // Handle FOCUS 1.0-preview PricingCategory values\r\n PricingCategory == 'On-Demand', 'Standard',\r\n PricingCategory == 'Commitment-Based', 'Committed',\r\n PricingCategory\r\n ),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n // Handle missing PublisherName values\r\n PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, ''),\r\n // Handle FOCUS 1.0-preview Region column\r\n RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region)),\r\n RegionName = coalesce(RegionName, Region),\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SkuId,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType, // Azure 1.0-preview(v1)+\r\n Tags = parse_json(Tags),\r\n x_AccountId, // Azure 1.0-preview(v1)+\r\n x_AccountName, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId, // Azure 1.0-preview(v1)+\r\n x_BilledCostInUsd, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement = case(\r\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\r\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\r\n ProviderName\r\n ), // Hubs add-on\r\n x_BillingAccountId, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate, // Azure 1.0-preview(v1)+\r\n x_BillingProfileId, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName, // Azure 1.0-preview(v1)+\r\n x_ChargeId, // Azure 1.0-preview(v1) only\r\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd), // Azure 1.0+\r\n x_CostAllocationRuleName, // Azure 1.0-preview(v1)+\r\n x_CostCategories = parse_json(x_CostCategories), // AWS 1.0 (JSON)\r\n x_CostCenter, // Azure 1.0-preview(v1)+\r\n x_Credits = parse_json(x_Credits), // GCP Jan 2024\r\n x_CostType, // GCP Jan 2024\r\n x_CurrencyConversionRate, // GCP Jun 2024\r\n x_CustomerId, // Azure 1.0-preview(v1)+\r\n x_CustomerName, // Azure 1.0-preview(v1)+\r\n x_Discount = parse_json(x_Discount), // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice, // Azure 1.0-preview(v1)+\r\n x_ExportTime, // GCP Jan 2024\r\n x_IngestionTime, // Hubs add-on\r\n x_InvoiceId = coalesce(InvoiceId, x_InvoiceId), // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId = case( // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId == '-2', '',\r\n x_InvoiceSectionId\r\n ),\r\n x_InvoiceSectionName = case( // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName == 'Unassigned', '',\r\n x_InvoiceSectionName\r\n ),\r\n x_ListCostInUsd, // Azure 1.0-preview(v1)+\r\n x_Location, // GCP Jan 2024\r\n x_Operation, // AWS 1.0\r\n x_PartnerCreditApplied, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency), // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription, // Azure 1.0-preview(v1)+\r\n x_Project, // GCP Jan 2024\r\n x_PublisherCategory, // Azure 1.0-preview(v1)+\r\n x_PublisherId, // Azure 1.0-preview(v1)+\r\n x_ResellerId, // Azure 1.0-preview(v1)+\r\n x_ResellerName, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName = tolower(x_ResourceGroupName), // Azure 1.0-preview(v1)+\r\n x_ResourceType, // Azure 1.0-preview(v1)+\r\n x_ServiceCode, // AWS 1.0\r\n x_ServiceId, // GCP Jan 2024\r\n x_ServicePeriodEnd, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart, // Azure 1.0-preview(v1)+\r\n x_SkuDescription, // Azure 1.0-preview(v1)+\r\n x_SkuDetails = parse_json(x_SkuDetails), // Azure 1.0-preview(v1)+\r\n x_SkuIsCreditEligible, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName = coalesce(SkuMeter, x_SkuMeterName), // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber, // Azure 1.0-preview(v1)+\r\n x_SkuRegion, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily, // Azure 1.0-preview(v1)+\r\n x_SkuTerm, // Azure 1.0-preview(v1)+\r\n x_SkuTier, // Azure 1.0-preview(v1)+\r\n x_SourceChanges, // Hubs add-on\r\n x_SourceName, // Hubs add-on\r\n x_SourceProvider, // Hubs add-on\r\n x_SourceType, // Hubs add-on\r\n x_SourceVersion, // Hubs add-on\r\n x_UsageType // AWS 1.0\r\n}\r\n\r\n// Costs_final_v1_0 table\r\n.create-merge table Costs_final_v1_0 (\r\n AvailabilityZone: string,\r\n BilledCost: decimal,\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingAccountType: string, // Azure 1.0-preview(v1)+\r\n BillingCurrency: string,\r\n BillingPeriodEnd: datetime,\r\n BillingPeriodStart: datetime,\r\n ChargeCategory: string,\r\n ChargeClass: string,\r\n ChargeDescription: string,\r\n ChargeFrequency: string,\r\n ChargePeriodEnd: datetime,\r\n ChargePeriodStart: datetime,\r\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview only\r\n CommitmentDiscountId: string,\r\n CommitmentDiscountName: string,\r\n CommitmentDiscountStatus: string,\r\n CommitmentDiscountType: string,\r\n ConsumedQuantity: decimal,\r\n ConsumedUnit: string,\r\n ContractedCost: decimal,\r\n ContractedUnitPrice: decimal,\r\n EffectiveCost: decimal,\r\n InvoiceIssuerName: string,\r\n ListCost: decimal,\r\n ListUnitPrice: decimal,\r\n PricingCategory: string,\r\n PricingQuantity: decimal,\r\n PricingUnit: string,\r\n ProviderName: string,\r\n PublisherName: string,\r\n RegionId: string,\r\n RegionName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n ServiceCategory: string,\r\n ServiceName: string,\r\n SkuId: string,\r\n SkuPriceId: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n SubAccountType: string,\r\n Tags: dynamic,\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_BilledCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: decimal, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: decimal, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_ContractedCostInUsd: decimal, // Azure 1.0+\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_Credits: dynamic, // GCP Jan 2024\r\n x_CostType: string, // GCP Jan 2024\r\n x_CurrencyConversionRate: decimal, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: dynamic, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: decimal, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_Operation: string, // AWS 1.0\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: decimal, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency: string, // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceChanges: string, // Hubs add-on\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Update policy for Costs_raw -> Costs_final_v1_0 table\r\n.alter table Costs_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Costs_raw\",\r\n \"Query\": \"Costs_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Actual costs |===================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use ActualCosts_transform_v1_2() instead.', folder='Costs')\r\nActualCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n // TODO: Transform actual costs to FOCUS 1.0 format\r\n ActualCosts_raw\r\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodStart = Date,\r\n ChargePeriodEnd = Date + 1d,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = '',\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory = '',\r\n SkuId = '',\r\n SkuMeter = '',\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = SubscriptionName,\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentName = '',\r\n x_ComponentType = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel = '',\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for ActualCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"ActualCosts_raw\",\r\n \"Query\": \"ActualCosts_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Amortized costs |================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use AmortizedCosts_transform_v1_2() instead.', folder='Costs')\r\nAmortizedCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n // TODO: Transform actual costs to FOCUS 1.0 format\r\n AmortizedCosts_raw\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodStart = Date,\r\n ChargePeriodEnd = Date + 1d,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = '',\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory = '',\r\n SkuId = '',\r\n SkuMeter = '',\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = SubscriptionName,\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentName = '',\r\n x_ComponentType = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel = '',\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for AmortizedCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"AmortizedCosts_raw\",\r\n \"Query\": \"AmortizedCosts_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All commitment discount usage transformed to FOCUS 1.0. This includes reservationdeatils_raw. Use CommitmentDiscountUsage_transform_v1_2() instead.', folder='Commitment discounts')\r\nCommitmentDiscountUsage_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n CommitmentDiscountUsage_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\r\n ReservedHours = todecimal(ReservedHours),\r\n TotalReservedQuantity = todecimal(TotalReservedQuantity),\r\n UsedHours = todecimal(UsedHours)\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Handle resource columns\r\n | extend ResourceId = tolower(InstanceId)\r\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\r\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\r\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\r\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\r\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\r\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, x_ServiceModel) on x_ResourceType\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n ChargePeriodEnd = UsageDate + 1d,\r\n ChargePeriodStart = UsageDate,\r\n CommitmentDiscountCategory = 'Usage',\r\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\r\n CommitmentDiscountType = 'Reservation',\r\n ConsumedQuantity = UsedHours,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\r\n x_CommitmentDiscountCommittedAmount = ReservedHours,\r\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\r\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\r\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\r\n x_CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\r\n x_IngestionTime = ingestion_time(),\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n // x_RowId = hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // CommitmentDiscountId,\r\n // ResourceId,\r\n // ChargePeriodStart\r\n // )),\r\n x_ServiceModel,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\r\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\r\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\r\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\r\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\r\n}\r\n\r\n// CommitmentDiscountUsage_final_v1_0 table\r\n.create-merge table CommitmentDiscountUsage_final_v1_0 (\r\n ChargePeriodEnd: datetime, // Hubs add-on\r\n ChargePeriodStart: datetime, // MS 2023-03-01\r\n CommitmentDiscountCategory: string, // Hubs add-on\r\n CommitmentDiscountId: string, // MS 2023-03-01\r\n CommitmentDiscountType: string, // Hubs add-on\r\n ConsumedQuantity: decimal, // MS 2023-03-01\r\n ProviderName: string, // Hubs add-on\r\n ResourceId: string, // MS 2023-03-01\r\n ResourceName: string, // Hubs add-on\r\n ResourceType: string, // Hubs add-on\r\n ServiceCategory: string, // Hubs add-on\r\n ServiceName: string, // Hubs add-on\r\n SubAccountId: string, // Hubs add-on\r\n x_CommitmentDiscountCommittedCount: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountCommittedAmount: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedRatio: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountQuantity: decimal, // MS 2023-03-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_ResourceGroupName: string, // Hubs add-on\r\n x_ResourceType: string, // Hubs add-on\r\n x_ServiceModel: string, // Hubs add-on\r\n x_SkuOrderId: string, // MS 2023-03-01\r\n x_SkuSize: string, // MS 2023-03-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string // Hubs add-on\r\n)\r\n\r\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_0 table\r\n.alter table CommitmentDiscountUsage_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"CommitmentDiscountUsage_raw\",\r\n \"Query\": \"CommitmentDiscountUsage_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All recommendations transformed to FOCUS 1.0. Use Recommendations_transform_v1_2() instead.', folder='Recommendations')\r\nRecommendations_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Recommendations_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n CostWithNoReservedInstances = todecimal(CostWithNoReservedInstances),\r\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\r\n NetSavings = todecimal(NetSavings),\r\n RecommendedQuantity = todecimal(RecommendedQuantity),\r\n RecommendedQuantityNormalized = todecimal(RecommendedQuantityNormalized),\r\n TotalCostWithReservedInstances = todecimal(TotalCostWithReservedInstances)\r\n //\r\n | extend x_IngestionTime = ingestion_time()\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Convert JSON cost columns to decimal\r\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\r\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\r\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\r\n //\r\n // Build recommendation details\r\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\r\n | extend x_RecommendationDetails = case(\r\n x_SourceType == 'ReservationRecommendations', bag_pack(\r\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\r\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\r\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\r\n 'CommitmentDiscountResourceType', ResourceType,\r\n 'CommitmentDiscountScope', Scope,\r\n 'LookbackPeriodDuration', case(\r\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\r\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\r\n ''\r\n ),\r\n 'LookbackPeriodStart', FirstUsageDate,\r\n 'RecommendedQuantity', RecommendedQuantity,\r\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\r\n 'RegionId', Location,\r\n 'RegionName', RegionName,\r\n 'SkuMeterId', MeterId,\r\n 'SkuPriceDetails', SkuProperties,\r\n 'SkuSize', coalesce(SKU, SkuName),\r\n 'SkuTerm', isoMonths(Term)\r\n ),\r\n dynamic({})\r\n )\r\n //\r\n // Sort columns and apply final transforms\r\n | extend x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d)\r\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\r\n | project\r\n ProviderName,\r\n SubAccountId = iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), ''),\r\n x_IngestionTime,\r\n x_EffectiveCostAfter = TotalCostWithReservedInstances,\r\n x_EffectiveCostBefore = CostWithNoReservedInstances,\r\n x_EffectiveCostSavings = NetSavings,\r\n x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d),\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n// Recommendations_final_v1_0 table\r\n.create-merge table Recommendations_final_v1_0 (\r\n ProviderName: string,\r\n SubAccountId: string,\r\n x_IngestionTime: datetime,\r\n x_EffectiveCostAfter: decimal,\r\n x_EffectiveCostBefore: decimal,\r\n x_EffectiveCostSavings: decimal,\r\n x_RecommendationDate: datetime,\r\n x_RecommendationDetails: dynamic,\r\n x_SourceName: string,\r\n x_SourceProvider: string,\r\n x_SourceType: string,\r\n x_SourceVersion: string\r\n)\r\n\r\n// Update policy for Recommendations_raw -> Recommendations_final_v1_0 table\r\n.alter table Recommendations_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Recommendations_raw\",\r\n \"Query\": \"Recommendations_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All transactions transformed to FOCUS 1.0. Use Transactions_transform_v1_2() instead.', folder='Transactions')\r\nTransactions_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Transactions_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n Amount = todecimal(Amount),\r\n MonetaryCommitment = todecimal(MonetaryCommitment),\r\n Overage = todecimal(Overage),\r\n Quantity = todecimal(Quantity)\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Handle BillingPeriodStart/End\r\n | extend BillingMonth = tostring(BillingMonth)\r\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\r\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n BilledCost = Amount,\r\n BillingAccountId = case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\r\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\r\n ''\r\n ),\r\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\r\n BillingCurrency = Currency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory = case(\r\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = case(\r\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\r\n EventType == 'Refund', 'Correction',\r\n ''\r\n ),\r\n ChargeDescription = Description,\r\n ChargeFrequency = case(\r\n BillingFrequency == 'OneTime', 'One-Time',\r\n BillingFrequency == 'Recurring', 'Recurring',\r\n BillingFrequency\r\n ),\r\n ChargePeriodStart = EventDate,\r\n PricingQuantity = Quantity,\r\n PricingUnit = 'Reservations',\r\n ProviderName,\r\n RegionId = Region,\r\n RegionName = Region,\r\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\r\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerEmail,\r\n x_CostCenter = CostCenter,\r\n x_InvoiceId = InvoiceId,\r\n x_InvoiceNumber = Invoice,\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\r\n x_IngestionTime = ingestion_time(),\r\n x_MonetaryCommitment = MonetaryCommitment,\r\n x_Overage = Overage,\r\n x_PurchasingBillingAccountId = PurchasingEnrollment,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuOrderName = ReservationOrderName,\r\n x_SkuSize = ArmSkuName,\r\n x_SkuTerm = isoMonths(Term),\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId = PurchasingSubscriptionGuid,\r\n x_TransactionType = EventType\r\n}\r\n\r\n// Transactions_final_v1_0 table\r\n.create-merge table Transactions_final_v1_0 (\r\n BilledCost: decimal, // MS CM EA+MCA 2023-05-01\r\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\r\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\r\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n ChargeCategory: string, // Hubs add-on\r\n ChargeClass: string, // Hubs add-on\r\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\r\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\r\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n PricingQuantity: decimal, // MS CM EA+MCA 2023-05-01\r\n PricingUnit: string, // Hubs add-on\r\n ProviderName: string, // Hubs add-on\r\n RegionId: string, // MS CM EA+MCA 2023-05-01\r\n RegionName: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\r\n x_AccountName: string, // MS CM EA 2023-05-01\r\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\r\n x_CostCenter: string, // MS CM EA 2023-05-01\r\n x_InvoiceId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_MonetaryCommitment: decimal, // MS CM EA 2023-05-01\r\n x_Overage: decimal, // MS CM EA 2023-05-01\r\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\r\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\r\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\r\n)\r\n\r\n// Update policy for Transactions_raw -> Transactions_final_v1_0 table\r\n.alter table Transactions_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Transactions_raw\",\r\n \"Query\": \"Transactions_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n", + "dataExplorerPrivateDnsZoneName": "[replace(format('privatelink.{0}.{1}', parameters('location'), replace(environment().suffixes.storage, 'core', 'kusto')), '..', '.')]", + "ingestionCapacity": { + "Dev(No SLA)_Standard_E2a_v4": 1, + "Dev(No SLA)_Standard_D11_v2": 1, + "Standard_D11_v2": 2, + "Standard_D12_v2": 4, + "Standard_D13_v2": 8, + "Standard_D14_v2": 16, + "Standard_D16d_v5": 16, + "Standard_D32d_v4": 32, + "Standard_D32d_v5": 32, + "Standard_DS13_v2+1TB_PS": 8, + "Standard_DS13_v2+2TB_PS": 8, + "Standard_DS14_v2+3TB_PS": 16, + "Standard_DS14_v2+4TB_PS": 16, + "Standard_E2a_v4": 2, + "Standard_E2ads_v5": 2, + "Standard_E2d_v4": 2, + "Standard_E2d_v5": 2, + "Standard_E4a_v4": 4, + "Standard_E4ads_v5": 4, + "Standard_E4d_v4": 4, + "Standard_E4d_v5": 4, + "Standard_E8a_v4": 8, + "Standard_E8ads_v5": 8, + "Standard_E8as_v4+1TB_PS": 8, + "Standard_E8as_v4+2TB_PS": 8, + "Standard_E8as_v5+1TB_PS": 8, + "Standard_E8as_v5+2TB_PS": 8, + "Standard_E8d_v4": 8, + "Standard_E8d_v5": 8, + "Standard_E8s_v4+1TB_PS": 8, + "Standard_E8s_v4+2TB_PS": 8, + "Standard_E8s_v5+1TB_PS": 8, + "Standard_E8s_v5+2TB_PS": 8, + "Standard_E16a_v4": 16, + "Standard_E16ads_v5": 16, + "Standard_E16as_v4+3TB_PS": 16, + "Standard_E16as_v4+4TB_PS": 16, + "Standard_E16as_v5+3TB_PS": 16, + "Standard_E16as_v5+4TB_PS": 16, + "Standard_E16d_v4": 16, + "Standard_E16d_v5": 16, + "Standard_E16s_v4+3TB_PS": 16, + "Standard_E16s_v4+4TB_PS": 16, + "Standard_E16s_v5+3TB_PS": 16, + "Standard_E16s_v5+4TB_PS": 16, + "Standard_E64i_v3": 64, + "Standard_E80ids_v4": 80, + "Standard_EC8ads_v5": 8, + "Standard_EC8as_v5+1TB_PS": 8, + "Standard_EC8as_v5+2TB_PS": 8, + "Standard_EC16ads_v5": 16, + "Standard_EC16as_v5+3TB_PS": 16, + "Standard_EC16as_v5+4TB_PS": 16, + "Standard_L4s": 4, + "Standard_L8as_v3": 8, + "Standard_L8s": 8, + "Standard_L8s_v2": 8, + "Standard_L8s_v3": 8, + "Standard_L16as_v3": 16, + "Standard_L16s": 16, + "Standard_L16s_v2": 16, + "Standard_L16s_v3": 16, + "Standard_L32as_v3": 32, + "Standard_L32s_v3": 32 + } + }, + "resources": [ + { + "type": "Microsoft.Kusto/clusters/principalAssignments", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('clusterName'), 'adf-mi-cluster-admin')]", + "properties": { + "principalType": "App", + "principalId": "[reference(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), '2018-06-01', 'full').identity.principalId]", + "tenantId": "[reference(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), '2018-06-01', 'full').identity.tenantId]", + "role": "AllDatabasesAdmin" + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + ] + }, + { + "type": "Microsoft.Kusto/clusters/databases", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('clusterName'), 'Ingestion')]", + "location": "[parameters('location')]", + "kind": "ReadWrite", + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + ] + }, + { + "type": "Microsoft.Kusto/clusters/databases", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('clusterName'), 'Hub')]", + "location": "[parameters('location')]", + "kind": "ReadWrite", + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + ] + }, + { + "type": "Microsoft.Kusto/clusters", + "apiVersion": "2023-08-15", + "name": "[parameters('clusterName')]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Kusto/clusters'), createObject()))]", + "sku": { + "name": "[parameters('clusterSku')]", + "tier": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 'Basic', 'Standard')]", + "capacity": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 1, if(equals(parameters('clusterCapacity'), 1), 2, parameters('clusterCapacity')))]" + }, + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "enableStreamingIngest": true, + "enableAutoStop": false, + "publicNetworkAccess": "[if(parameters('enablePublicAccess'), 'Enabled', 'Disabled')]" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(parameters('clusterName'), subscription().id, 'Storage Blob Data Contributor')]", + "properties": { + "description": "Give \"Storage Blob Data Contributor\" to the cluster", + "principalId": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15', 'full').identity.principalId]", + "principalType": "ServicePrincipal", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + ] + }, + { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[variables('dataExplorerPrivateDnsZoneName')]", + "location": "global", + "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateDnsZones'), createObject()))]", + "properties": {} + }, + { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', variables('dataExplorerPrivateDnsZoneName'), format('{0}-link', replace(variables('dataExplorerPrivateDnsZoneName'), '.', '-')))]", + "location": "global", + "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks'), createObject()))]", + "properties": { + "virtualNetwork": { + "id": "[parameters('virtualNetworkId')]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" + ] + }, + { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('clusterName'))]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateEndpoints'), createObject()))]", + "properties": { + "subnet": { + "id": "[parameters('privateEndpointSubnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "dataExplorerLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "groupIds": [ + "cluster" + ] } } - ], - "parameters": { - "folderPath": { - "type": "string" - }, - "fileName": { - "type": "string" + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + ] + }, + { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('clusterName')), 'dataExplorer-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "privatelink-westus-kusto-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" + } }, - "originalFileName": { - "type": "string" + { + "name": "privatelink-blob-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } }, - "ingestionId": { - "type": "string" + { + "name": "privatelink-table-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.table.{0}', environment().suffixes.storage))]" + } }, - "table": { - "type": "string" + { + "name": "privatelink-queue-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" + } } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-ep', parameters('clusterName')))]", + "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ingestion_OpenDataInternalScripts", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "variables": { - "tryAgain": { - "type": "Boolean", - "defaultValue": true + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('clusterName')]" }, - "logRetentionDays": { - "type": "Integer", - "defaultValue": 0 + "databaseName": { + "value": "Ingestion" }, - "finalRetentionMonths": { - "type": "Integer", - "defaultValue": 999 + "scripts": { + "value": { + "OpenDataFunctions_resource_type_1": "[variables('$fxv#0')]", + "OpenDataFunctions_resource_type_2": "[variables('$fxv#1')]", + "OpenDataFunctions_resource_type_3": "[variables('$fxv#2')]", + "OpenDataFunctions_resource_type_4": "[variables('$fxv#3')]", + "OpenDataFunctions_resource_type_5": "[variables('$fxv#4')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, - "annotations": [] + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" + } + }, + "parameters": { + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer instance." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + } + } + }, + "resources": [ + { + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" + }, + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" + } + } + ] + } }, "dependsOn": [ - "appRegistration", - "linkedService_dataExplorer" - ], - "metadata": { - "description": "Ingests parquet data into an Azure Data Explorer cluster." - } + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]" + ] }, - "pipeline_ExecuteIngestionETL": { - "condition": "[or(variables('useAzure'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ExecuteETL', variables('INGESTION')))]", + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ingestion_InitScripts", "properties": { - "concurrency": 1, - "activities": [ - { - "name": "Wait", - "description": "Files may not be available immediately after being created.", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 60 + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('clusterName')]" + }, + "databaseName": { + "value": "Ingestion" + }, + "scripts": { + "value": { + "openData": "[variables('$fxv#5')]", + "common": "[variables('$fxv#6')]", + "infra": "[variables('$fxv#7')]", + "rawTables": "[replace(variables('$fxv#8'), '$$rawRetentionInDays$$', string(parameters('rawRetentionInDays')))]" } }, - { - "name": "Set Container Folder Path", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Succeeded" - ] + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" + } + }, + "parameters": { + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer instance." } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "containerFolderPath", - "value": { - "value": "@join(skip(array(split(pipeline().parameters.folderPath, '/')), 1), '/')", - "type": "Expression" + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." } - } - }, - { - "name": "Get Existing Parquet Files", - "description": "Get the previously ingested files so we can get file paths.", - "type": "GetMetadata", - "dependsOn": [ - { - "activity": "Set Container Folder Path", - "dependencyConditions": [ - "Succeeded" - ] + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "ingestion_files", - "type": "DatasetReference", - "parameters": { - "folderPath": "@variables('containerFolderPath')" - } - }, - "fieldList": [ - "childItems" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, - { - "name": "Filter Out Folders", - "description": "Remove any folders or manifest files.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Get Existing Parquet Files", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", - "type": "Expression" + "resources": [ + { + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": { - "value": "@and(equals(item().type, 'File'), not(contains(toLower(item().name), 'manifest.json')))", - "type": "Expression" + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]", + "[resourceId('Microsoft.Resources/deployments', 'ingestion_OpenDataInternalScripts')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ingestion_VersionedScripts", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('clusterName')]" }, - { - "name": "Set Ingestion Timestamp", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Succeeded" - ] + "databaseName": { + "value": "Ingestion" + }, + "scripts": { + "value": { + "v1_0": "[variables('$fxv#9')]", + "v1_2": "[variables('$fxv#10')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" + } + }, + "parameters": { + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer instance." } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "timestamp", - "value": { - "value": "@utcNow()", - "type": "Expression" + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." } - } - }, - { - "name": "For Each Old File", - "description": "Loop thru each of the existing files.", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Filter Out Folders", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Ingestion Timestamp", - "dependencyConditions": [ - "Succeeded" - ] + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } - ], - "userProperties": [], - "typeProperties": { - "batchCount": "[variables('dataExplorerIngestionCapacity')]", - "items": { - "value": "@activity('Filter Out Folders').output.Value", - "type": "Expression" - }, - "activities": [ - { - "name": "Execute", - "description": "Run the ADX ETL pipeline.", - "type": "ExecutePipeline", - "dependsOn": [], - "policy": { - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_ETL_dataExplorer', variables('INGESTION'))]", - "type": "PipelineReference" - }, - "waitOnCompletion": true, - "parameters": { - "folderPath": { - "value": "@variables('containerFolderPath')", - "type": "Expression" - }, - "fileName": { - "value": "@item().name", - "type": "Expression" - }, - "originalFileName": { - "value": "[format('@last(array(split(item().name, ''{0}'')))', variables('INGESTION_ID_SEPARATOR'))]", - "type": "Expression" - }, - "ingestionId": { - "value": "[format('@concat(first(array(split(item().name, ''{0}''))), ''_'', variables(''timestamp''))', variables('INGESTION_ID_SEPARATOR'))]", - "type": "Expression" - }, - "table": { - "value": "@concat(first(array(split(variables('containerFolderPath'), '/'))), '_raw')", - "type": "Expression" - } - } - } - } - ] } }, - { - "name": "If No Files", - "description": "If there are no files found, fail the pipeline.", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Filter Out Folders", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@equals(length(activity('Filter Out Folders').output.Value), 0)", - "type": "Expression" + "resources": [ + { + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "ifTrueActivities": [ - { - "name": "Files Not Found", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to locate parquet files to ingest from the ', pipeline().parameters.folderPath, ' path. Please confirm the folder path is the full path, including the \"ingestion\" container and not starting with or ending with a slash (\"/\").')", - "type": "Expression" - }, - "errorCode": "IngestionFilesNotFound" - } - } - ] + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" + } } - } - ], - "parameters": { - "folderPath": { - "type": "string" - } - }, - "variables": { - "containerFolderPath": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - }, - "annotations": [ - "New ingestion" - ] + ] + } }, "dependsOn": [ - "appRegistration", - "pipeline_ToDataExplorer" - ], - "metadata": { - "description": "Queues the ingestion_ETL_dataExplorer pipeline to account for Data Factory pipeline trigger limits." - } + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]", + "[resourceId('Microsoft.Resources/deployments', 'ingestion_InitScripts')]" + ] }, - "appRegistration": { + { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_Register", + "apiVersion": "2022-09-01", + "name": "hub_InitScripts", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "app": { - "value": "[parameters('app')]" + "clusterName": { + "value": "[parameters('clusterName')]" }, - "version": { - "value": "[variables('finOpsToolkitVersion')]" + "databaseName": { + "value": "Hub" }, - "features": { - "value": [ - "DataFactory", - "Storage" - ] + "scripts": { + "value": { + "common": "[variables('$fxv#11')]", + "openData": "[variables('$fxv#12')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5436870138046688593" + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" } }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, + "parameters": { + "clusterName": { + "type": "string", "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the FinOps hub Data Explorer instance." } }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." } }, - "_1.IdNameObject": { + "scripts": { "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + } + } + }, + "resources": [ + { + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", + "[resourceId('Microsoft.Resources/deployments', 'ingestion_InitScripts')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "hub_VersionedScripts", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('clusterName')]" + }, + "databaseName": { + "value": "Hub" + }, + "scripts": { + "value": { + "v1_0": "[variables('$fxv#13')]", + "v1_2": "[variables('$fxv#14')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" + } + }, + "parameters": { + "clusterName": { + "type": "string", "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the FinOps hub Data Explorer instance." } }, - "HubAppFeature": { + "databaseName": { "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." } }, - "HubAppProperties": { + "scripts": { "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, - "functions": [ + "resources": [ { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" + }, + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", + "[resourceId('Microsoft.Resources/deployments', 'hub_InitScripts')]", + "[resourceId('Microsoft.Resources/deployments', 'ingestion_VersionedScripts')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "hub_LatestScripts", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('clusterName')]" + }, + "databaseName": { + "value": "Hub" + }, + "scripts": { + "value": { + "latest": "[variables('$fxv#15')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" + } + }, "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer instance." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, "metadata": { - "description": "Required. FinOps hub app getting deployed." + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." } }, - "version": { + "forceUpdateTag": { "type": "string", + "defaultValue": "[utcNow()]", "metadata": { - "description": "Required. Version number of the FinOps hub app." + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" + } + }, + "resources": [ + { + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", + "[resourceId('Microsoft.Resources/deployments', 'hub_VersionedScripts')]" + ] + } + ], + "outputs": { + "clusterId": { + "type": "string", + "metadata": { + "description": "The resource ID of the cluster." + }, + "value": "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "The ID of the cluster system assigned managed identity." + }, + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15', 'full').identity.principalId]" + }, + "clusterName": { + "type": "string", + "metadata": { + "description": "The name of the cluster." + }, + "value": "[parameters('clusterName')]" + }, + "clusterUri": { + "type": "string", + "metadata": { + "description": "The URI of the cluster." + }, + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15').uri]" + }, + "ingestionDbName": { + "type": "string", + "metadata": { + "description": "The name of the database for data ingestion." + }, + "value": "Ingestion" + }, + "hubDbName": { + "type": "string", + "metadata": { + "description": "The name of the database for queries." + }, + "value": "Hub" + }, + "clusterIngestionCapacity": { + "type": "int", + "metadata": { + "description": "Max ingestion capacity of the cluster." + }, + "value": "[coalesce(tryGet(variables('ingestionCapacity'), parameters('clusterSku')), 1)]" + } + } + } + }, + "dependsOn": [ + "core", + "infrastructure" + ] + }, + "dataFactoryResources": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "dataFactoryResources", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft FinOps hubs', 'Microsoft.FinOpsHubs', 'DataFactory', 'FinOps hub engine', variables('$fxv#1'))]" + }, + "hubName": { + "value": "[parameters('hubName')]" + }, + "dataFactoryName": { + "value": "[reference('core').outputs.dataFactoryName.value]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[reference('core').outputs.publisherTags.value]" + }, + "tagsByResource": { + "value": "[parameters('tagsByResource')]" + }, + "storageAccountName": { + "value": "[reference('core').outputs.storageAccountName.value]" + }, + "exportContainerName": { + "value": "[reference('cmExports').outputs.exportContainer.value]" + }, + "configContainerName": { + "value": "[reference('core').outputs.configContainer.value]" + }, + "ingestionContainerName": { + "value": "[reference('core').outputs.ingestionContainer.value]" + }, + "dataExplorerName": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterName.value))]", + "dataExplorerPrincipalId": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.principalId.value))]", + "dataExplorerIngestionDatabase": "[if(variables('useFabric'), createObject('value', 'Ingestion'), if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.ingestionDbName.value)))]", + "dataExplorerIngestionCapacity": "[if(variables('useFabric'), createObject('value', parameters('fabricCapacityUnits')), if(not(variables('deployDataExplorer')), createObject('value', 1), createObject('value', reference('dataExplorer').outputs.clusterIngestionCapacity.value)))]", + "dataExplorerUri": "[if(variables('useFabric'), createObject('value', parameters('fabricQueryUri')), if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterUri.value)))]", + "dataExplorerId": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterId.value))]", + "enableManagedExports": { + "value": "[parameters('enableManagedExports')]" + }, + "enablePublicAccess": { + "value": "[parameters('enablePublicAccess')]" + }, + "keyVaultName": "[if(empty(parameters('remoteHubStorageKey')), createObject('value', ''), createObject('value', reference('remoteHub').outputs.keyVaultName.value))]", + "remoteHubStorageUri": { + "value": "[parameters('remoteHubStorageUri')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "11163540491967572356" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "storageRoles": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." - } + "keyVaultSku": { + "type": "string" }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0}', parameters('app').id)]", - "version": "[parameters('version')]" - } - }, - "resources": [] - } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", - "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" - }, - "resources": { - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", - "properties": { - "name": "[parameters('app').storage]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "storageAccount" - ] + "displayName": { + "type": "string" }, - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", - "properties": { - "name": "[parameters('app').keyVault]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "keyVault" - ] + "suffix": { + "type": "string" }, - "dataFactory::managedVirtualNetwork": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "properties": {}, - "dependsOn": [ - "dataFactory" - ] + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getExportBody": { + "parameters": [ + { + "type": "string", + "name": "exportContainerName" }, - "dataFactory::managedIntegrationRuntime": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", - "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "default", - "type": "ManagedVirtualNetworkReference" - }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('app').hub.location]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 - } - } - } - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedVirtualNetwork" - ] + { + "type": "string", + "name": "datasetType" }, - "dataFactory::linkedService_keyVault": { - "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "keyVault" - ] + { + "type": "string", + "name": "schemaVersion" }, - "dataFactory::linkedService_storageAccount": { - "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "storageAccount" - ] + { + "type": "bool", + "name": "isMonthly" }, - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] + { + "type": "string", + "name": "exportFormat" }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] + { + "type": "string", + "name": "compressionMode" }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] + { + "type": "string", + "name": "partitionData" }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] + { + "type": "string", + "name": "dataOverwriteBehavior" + } + ], + "output": { + "type": "string", + "value": "[format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}\", \"name\": \"@{{variables(''exportName'')}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'))]" + } + }, + "getExportBodyV2": { + "parameters": [ + { + "type": "string", + "name": "exportContainerName" }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] + { + "type": "string", + "name": "datasetType" }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" - } - } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] + { + "type": "string", + "name": "schemaVersion" }, - "appTelemetry": { - "condition": "[parameters('app').hub.options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", - "properties": "[variables('telemetryProps')]" + { + "type": "bool", + "name": "isMonthly" }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" - } - } + { + "type": "string", + "name": "exportFormat" }, - "storageRoleAssignments": { - "copy": { - "name": "storageRoleAssignments", - "count": "[length(variables('factoryStorageRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "storageAccount" - ] + { + "type": "string", + "name": "compressionMode" }, - "triggerManagerIdentity": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "dependsOn": [ - "dataFactory" - ] + { + "type": "string", + "name": "partitionData" }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "triggerManagerIdentity" - ] + { + "type": "string", + "name": "dataOverwriteBehavior" }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "location": "[parameters('app').hub.location]", - "sku": { - "name": "[parameters('app').hub.options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" + { + "type": "string", + "name": "recommendationScope" }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + { + "type": "string", + "name": "recommendationLookbackPeriod" }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "blob" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "string", + "value": "[if(equals(toLower(parameters('datasetType')), 'focuscost'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{10}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), if(equals(toLower(parameters('datasetType')), 'reservationdetails'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(or(equals(toLower(parameters('datasetType')), 'pricesheet'), equals(toLower(parameters('datasetType')), 'reservationtransactions')), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}}}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheCurrentMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(equals(toLower(parameters('datasetType')), 'reservationrecommendations'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [ {{ \"name\": \"reservationScope\", \"value\": \"{1}\" }}, {{ \"name\": \"resourceType\", \"value\": \"{2}\" }}, {{ \"name\": \"lookBackPeriod\", \"value\": \"{3}\" }}] }}}}, \"timeframe\": \"{4}\", \"type\": \"{5}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{6}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{7}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{8}\", \"partitionData\": \"{9}\", \"dataOverwriteBehavior\": \"{10}\", \"compressionMode\": \"{11}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{12}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{13}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), parameters('recommendationScope'), parameters('resourceType'), parameters('recommendationLookbackPeriod'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), 'undefined'))))]" + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. Temporary app placeholder for the deployments module." + } + }, + "hubName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub instance." + } + }, + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory instance." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. The name of the Azure Key Vault instance." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. The name of the Azure storage account instance." + } + }, + "exportContainerName": { + "type": "string", + "metadata": { + "description": "Required. The name of the container where Cost Management data is exported." + } + }, + "ingestionContainerName": { + "type": "string", + "metadata": { + "description": "Required. The name of the container where normalized data is ingested." + } + }, + "configContainerName": { + "type": "string", + "metadata": { + "description": "Required. The name of the container where normalized data is ingested." + } + }, + "dataExplorerName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics, if applicable." + } + }, + "dataExplorerId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of the Azure Data Explorer cluster to use for advanced analytics, if applicable." + } + }, + "dataExplorerPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. ID of the Azure Data Explorer cluster system assigned managed identity, if applicable." + } + }, + "dataExplorerUri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. URI of the Azure Data Explorer cluster or Microsoft Fabric eventhouse query endpoint to use for advanced analytics, if applicable." + } + }, + "dataExplorerIngestionDatabase": { + "type": "string", + "defaultValue": "Ingestion", + "metadata": { + "description": "Optional. Name of the Azure Data Explorer ingestion database. Default: \"ingestion\"." + } + }, + "dataExplorerIngestionCapacity": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. Azure Data Explorer ingestion capacity or Microsoft Fabric capacity units. Increase for non-dev/trial SKUs. Default: 1" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." + } + }, + "remoteHubStorageUri": { + "type": "string", + "metadata": { + "description": "Optional. Remote storage account for ingestion dataset." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to all resources." + } + }, + "tagsByResource": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." + } + }, + "enableManagedExports": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable managed exports where your FinOps hub instance will create and run Cost Management exports on your behalf. Not supported for Microsoft Customer Agreement (MCA) billing profiles. Requires the ability to grant User Access Administrator role to FinOps hubs, which is required to create Cost Management exports. Default: true." + } + }, + "enablePublicAccess": { + "type": "bool", + "metadata": { + "description": "Required. Enable public access." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\n#\r\n$adfParams = @{\r\n ResourceGroupName = $env:DataFactoryResourceGroup\r\n DataFactoryName = $env:DataFactoryName\r\n}\r\n\r\n# Delete old triggers\r\n$triggers = Get-AzDataFactoryV2Trigger @adfParams -ErrorAction SilentlyContinue `\r\n| Where-Object { $_.Name -match '^msexports(_(setup|daily|monthly|extract|FileAdded))?$' }\r\n$DeploymentScriptOutputs[\"stopTriggers\"] = $triggers | Stop-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\r\n$DeploymentScriptOutputs[\"deleteTriggers\"] = $triggers | Remove-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\r\n\r\n# Delete old pipelines\r\n$DeploymentScriptOutputs[\"pipelines\"] = Get-AzDataFactoryV2Pipeline @adfParams -ErrorAction SilentlyContinue `\r\n| Where-Object { $_.Name -match '^(msexports_(backfill|extract|fill|get|run|setup|transform)|config_(BackfillData|ExportData|RunBackfill|RunExports))$' } `\r\n| Remove-AzDataFactoryV2Pipeline -Force -ErrorAction SilentlyContinue\r\n", + "$fxv#1": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nParam(\r\n [switch] $Stop\r\n)\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\nif (-not $Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\n# Loop thru triggers\r\n$env:Triggers.Split('|') `\r\n| ForEach-Object {\r\n $trigger = $_\r\n if ($Stop)\r\n {\r\n Write-Output \"Stopping trigger $trigger...\"\r\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force `\r\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\r\n }\r\n else\r\n {\r\n Write-Output \"Starting trigger $trigger...\"\r\n $triggerOutput = Start-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force\r\n }\r\n if ($triggerOutput)\r\n {\r\n Write-Output \"done...\"\r\n }\r\n else\r\n {\r\n Write-Output \"failed...\"\r\n }\r\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\r\n}\r\n\r\nif ($Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\r\n{\r\n $env:Pipelines.Split('|') `\r\n | ForEach-Object {\r\n Write-Output \"Running the init pipeline...\"\r\n Invoke-AzDataFactoryV2Pipeline `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -PipelineName $_\r\n }\r\n}\r\n", + "$fxv#2": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nParam(\r\n [switch] $Stop\r\n)\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\nif (-not $Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\n# Loop thru triggers\r\n$env:Triggers.Split('|') `\r\n| ForEach-Object {\r\n $trigger = $_\r\n if ($Stop)\r\n {\r\n Write-Output \"Stopping trigger $trigger...\"\r\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force `\r\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\r\n }\r\n else\r\n {\r\n Write-Output \"Starting trigger $trigger...\"\r\n $triggerOutput = Start-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force\r\n }\r\n if ($triggerOutput)\r\n {\r\n Write-Output \"done...\"\r\n }\r\n else\r\n {\r\n Write-Output \"failed...\"\r\n }\r\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\r\n}\r\n\r\nif ($Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\r\n{\r\n $env:Pipelines.Split('|') `\r\n | ForEach-Object {\r\n Write-Output \"Running the init pipeline...\"\r\n Invoke-AzDataFactoryV2Pipeline `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -PipelineName $_\r\n }\r\n}\r\n", + "focusSchemaVersion": "1.0", + "exportSchemaVersion": "2023-05-01", + "reservationDetailsSchemaVersion": "2023-03-01", + "ftkVersion": "12.0", + "ftkReleaseUri": "[if(endsWith(variables('ftkVersion'), '-dev'), 'https://github.com/microsoft/finops-toolkit/releases/latest/download', format('https://github.com/microsoft/finops-toolkit/releases/download/v{0}', variables('ftkVersion')))]", + "exportApiVersion": "2023-07-01-preview", + "hubDataExplorerName": "hubDataExplorer", + "deployDataExplorer": "[not(empty(parameters('dataExplorerId')))]", + "useFabric": "[and(not(variables('deployDataExplorer')), not(empty(parameters('dataExplorerUri'))))]", + "datasetPropsDefault": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().fileName}", + "type": "Expression" + }, + "folderPath": { + "value": "@{dataset().folderPath}", + "type": "Expression" + } + } + }, + "safeExportContainerName": "[replace(format('{0}', parameters('exportContainerName')), '-', '_')]", + "safeIngestionContainerName": "[replace(format('{0}', parameters('ingestionContainerName')), '-', '_')]", + "safeConfigContainerName": "[replace(format('{0}', parameters('configContainerName')), '-', '_')]", + "managedVnetName": "default", + "ingestionIdFileNameSeparator": "__", + "exportManifestAddedTriggerName": "[format('{0}_ManifestAdded', variables('safeExportContainerName'))]", + "ingestionManifestAddedTriggerName": "[format('{0}_ManifestAdded', variables('safeIngestionContainerName'))]", + "updateConfigTriggerName": "[format('{0}_SettingsUpdated', variables('safeConfigContainerName'))]", + "dailyTriggerName": "[format('{0}_DailySchedule', variables('safeConfigContainerName'))]", + "monthlyTriggerName": "[format('{0}_MonthlySchedule', variables('safeConfigContainerName'))]", + "allHubTriggers": [ + "[variables('exportManifestAddedTriggerName')]", + "[variables('ingestionManifestAddedTriggerName')]", + "[variables('updateConfigTriggerName')]", + "[variables('dailyTriggerName')]", + "[variables('monthlyTriggerName')]" + ], + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "storageRbacRoles": "[union(createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'), if(not(parameters('enableManagedExports')), createArray(), createArray('18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')))]", + "adxRbacRoles": [ + "b24988ac-6180-42a0-ab88-20f7382dd24c" + ] + }, + "resources": { + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('dataFactoryName')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('storageAccountName')]" + }, + "keyVault": { + "condition": "[not(empty(parameters('remoteHubStorageUri')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('keyVaultName')]" + }, + "dataExplorerCluster": { + "condition": "[variables('deployDataExplorer')]", + "existing": true, + "type": "Microsoft.Kusto/clusters", + "apiVersion": "2023-08-15", + "name": "[parameters('dataExplorerName')]" + }, + "managedVirtualNetwork": { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('managedVnetName'))]", + "properties": {} + }, + "managedIntegrationRuntime": { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "[variables('managedVnetName')]", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('location')]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "managedVirtualNetwork" + ] + }, + "storageManagedPrivateEndpoint": { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), parameters('storageAccountName'))]", + "properties": { + "name": "[parameters('storageAccountName')]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "managedVirtualNetwork", + "storageAccount" + ] + }, + "keyVaultManagedPrivateEndpoint": { + "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), parameters('keyVaultName'))]", + "properties": { + "name": "[parameters('keyVaultName')]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "keyVault", + "managedVirtualNetwork" + ] + }, + "dataExplorerManagedPrivateEndpoint": { + "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), variables('hubDataExplorerName'))]", + "properties": { + "name": "[variables('hubDataExplorerName')]", + "groupId": "cluster", + "privateLinkResourceId": "[parameters('dataExplorerId')]", + "fqdns": [ + "[parameters('dataExplorerUri')]" + ] + }, + "dependsOn": [ + "managedVirtualNetwork" + ] + }, + "triggerManagerIdentity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('dataFactoryName'))]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]" + }, + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('dataFactoryName'))]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('dataFactoryName'))))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "triggerManagerIdentity" + ] + }, + "factoryIdentityStorageRoleAssignments": { + "copy": { + "name": "factoryIdentityStorageRoleAssignments", + "count": "[length(variables('storageRbacRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), variables('storageRbacRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('storageRbacRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory" + ] + }, + "factoryIdentityDataExplorerRoleAssignments": { + "copy": { + "name": "factoryIdentityDataExplorerRoleAssignments", + "count": "[length(variables('adxRbacRoles'))]" + }, + "condition": "[variables('deployDataExplorer')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Kusto/clusters/{0}', parameters('dataExplorerName'))]", + "name": "[guid(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), variables('adxRbacRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('adxRbacRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory" + ] + }, + "linkedService_keyVault": { + "condition": "[not(empty(parameters('remoteHubStorageUri')))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('keyVaultName'))]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('keyVaultName')), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" + }, + "dependsOn": [ + "managedIntegrationRuntime" + ] + }, + "linkedService_storageAccount": { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('storageAccountName'))]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName')), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" + }, + "dependsOn": [ + "managedIntegrationRuntime" + ] + }, + "linkedService_dataExplorer": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('hubDataExplorerName'))]", + "properties": { + "type": "AzureDataExplorer", + "parameters": { + "database": { + "type": "String", + "defaultValue": "[parameters('dataExplorerIngestionDatabase')]" + } + }, + "typeProperties": { + "endpoint": "[parameters('dataExplorerUri')]", + "database": "@{linkedService().database}", + "tenant": "[reference('dataFactory', '2018-06-01', 'full').identity.tenantId]", + "servicePrincipalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + }, + "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" + }, + "dependsOn": [ + "dataFactory", + "managedIntegrationRuntime" + ] + }, + "linkedService_remoteHubStorage": { + "condition": "[not(empty(parameters('remoteHubStorageUri')))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'remoteHubStorage')]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[parameters('remoteHubStorageUri')]", + "accountKey": { + "type": "AzureKeyVaultSecret", + "store": { + "referenceName": "[parameters('keyVaultName')]", + "type": "LinkedServiceReference" + }, + "secretName": "[format('{0}-storage-key', toLower(parameters('hubName')))]" + } + }, + "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" + }, + "dependsOn": [ + "linkedService_keyVault", + "managedIntegrationRuntime" + ] + }, + "linkedService_ftkRepo": { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ftkRepo')]", + "properties": { + "parameters": { + "filePath": { + "type": "string" + } + }, + "annotations": [], + "type": "HttpServer", + "typeProperties": { + "url": "@concat('https://github.com/microsoft/finops-toolkit/', linkedService().filePath)", + "enableServerCertificateValidation": true, + "authenticationType": "Anonymous" + }, + "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" + }, + "dependsOn": [ + "managedIntegrationRuntime" + ] + }, + "dataset_config": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeConfigContainerName'))]", + "properties": { + "annotations": [], + "parameters": { + "fileName": { + "type": "String", + "defaultValue": "settings.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[parameters('configContainerName')]" + } + }, + "type": "Json", + "typeProperties": "[variables('datasetPropsDefault')]", + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('storageAccountName')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_storageAccount" + ] + }, + "dataset_manifest": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'manifest')]", + "properties": { + "annotations": [], + "parameters": { + "fileName": { + "type": "String", + "defaultValue": "manifest.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[parameters('exportContainerName')]" + } + }, + "type": "Json", + "typeProperties": "[variables('datasetPropsDefault')]", + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('storageAccountName')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_storageAccount" + ] + }, + "dataset_msexports": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeExportContainerName'))]", + "properties": { + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + "fileSystem": "[variables('safeExportContainerName')]" + }, + "columnDelimiter": ",", + "escapeChar": "\"", + "quoteChar": "\"", + "firstRowAsHeader": true + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('storageAccountName')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_storageAccount" + ] + }, + "dataset_msexports_gzip": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_gzip', variables('safeExportContainerName')))]", + "properties": { + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] + "fileSystem": "[variables('safeExportContainerName')]" + }, + "columnDelimiter": ",", + "escapeChar": "\"", + "quoteChar": "\"", + "firstRowAsHeader": true, + "compressionCodec": "Gzip" + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('storageAccountName')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_storageAccount" + ] + }, + "dataset_msexports_parquet": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_parquet', variables('safeExportContainerName')))]", + "properties": { + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", - "properties": { - "sku": { - "name": "[parameters('app').hub.options.keyVaultSku]", - "family": "A" - }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" - } - }, - "dependsOn": [ - "dataFactory" - ] + "fileSystem": "[variables('safeExportContainerName')]" + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('storageAccountName')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_storageAccount" + ] + }, + "dataset_ingestion": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeIngestionContainerName'))]", + "properties": { + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} + "fileSystem": "[variables('safeIngestionContainerName')]" + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[if(empty(parameters('remoteHubStorageUri')), parameters('storageAccountName'), 'remoteHubStorage')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_remoteHubStorage", + "linkedService_storageAccount" + ] + }, + "dataset_ingestion_files": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_files', variables('safeIngestionContainerName')))]", + "properties": { + "annotations": [], + "parameters": { + "folderPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileSystem": "[variables('safeIngestionContainerName')]", + "folderPath": { + "value": "@dataset().folderPath", + "type": "Expression" + } + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[if(empty(parameters('remoteHubStorageUri')), parameters('storageAccountName'), 'remoteHubStorage')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_remoteHubStorage", + "linkedService_storageAccount" + ] + }, + "dataset_dataExplorer": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('hubDataExplorerName'))]", + "properties": { + "type": "AzureDataExplorerTable", + "linkedServiceName": { + "parameters": { + "database": "@dataset().database" + }, + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference" + }, + "parameters": { + "database": { + "type": "String", + "defaultValue": "[parameters('dataExplorerIngestionDatabase')]" + }, + "table": { + "type": "String" + } + }, + "typeProperties": { + "table": { + "value": "@dataset().table", + "type": "Expression" + } + } + }, + "dependsOn": [ + "linkedService_dataExplorer" + ] + }, + "dataset_ftkReleaseFile": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ftkReleaseFile')]", + "properties": { + "linkedServiceName": { + "referenceName": "ftkRepo", + "type": "LinkedServiceReference" + }, + "parameters": { + "fileName": { + "type": "string" + }, + "version": { + "type": "string", + "defaultValue": "[variables('ftkVersion')]" + } + }, + "annotations": [], + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "HttpServerLocation", + "relativeUrl": { + "value": "@concat('releases/download/v', dataset().version, '/', dataset().fileName)", + "type": "Expression" + } + }, + "columnDelimiter": ",", + "escapeChar": "\\", + "firstRowAsHeader": true, + "quoteChar": "\"" + }, + "schema": [] + }, + "dependsOn": [ + "linkedService_ftkRepo" + ] + }, + "trigger_DailySchedule": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('dailyTriggerName'))]", + "properties": { + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[format('{0}_StartExportProcess', variables('safeConfigContainerName'))]", + "type": "PipelineReference" }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('app').keyVault)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.keyVault]" - }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] - }, - "dependsOn": [ - "keyVault" - ] + "parameters": { + "Recurrence": "Daily" + } + } + ], + "type": "ScheduleTrigger", + "typeProperties": { + "recurrence": { + "frequency": "Hour", + "interval": 24, + "startTime": "2023-01-01T01:01:00", + "timeZone": "[reference('azuretimezones').outputs.Timezone.value]" + } + } + }, + "dependsOn": [ + "azuretimezones", + "pipeline_StartExportProcess", + "stopTriggers" + ] + }, + "trigger_MonthlySchedule": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('monthlyTriggerName'))]", + "properties": { + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[format('{0}_StartExportProcess', variables('safeConfigContainerName'))]", + "type": "PipelineReference" }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", - "getStoragePrivateEndpointConnections", - "keyVault" + "parameters": { + "Recurrence": "Monthly" + } + } + ], + "type": "ScheduleTrigger", + "typeProperties": { + "recurrence": { + "frequency": "Month", + "interval": 1, + "startTime": "2023-01-05T01:11:00", + "timeZone": "[reference('azuretimezones').outputs.Timezone.value]", + "schedule": { + "monthDays": [ + 2, + 5, + 19 ] + } + } + } + }, + "dependsOn": [ + "azuretimezones", + "pipeline_StartExportProcess", + "stopTriggers" + ] + }, + "pipeline_InitializeHub": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_InitializeHub', variables('safeConfigContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Config", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } + "formatSettings": { + "type": "JsonReadSettings" } }, - "dependsOn": [ - "getKeyVaultPrivateEndpointConnections", - "keyVault" - ] - }, - "getStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference" + } + } + }, + { + "name": "Set Version", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "version", + "value": { + "value": "@activity('Get Config').output.firstRow.version", + "type": "Expression" + } + } + }, + { + "name": "Set Scopes", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "scopes", + "value": { + "value": "@string(activity('Get Config').output.firstRow.scopes)", + "type": "Expression" + } + } + }, + { + "name": "Set Retention", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "retention", + "value": { + "value": "@string(activity('Get Config').output.firstRow.retention)", + "type": "Expression" + } + } + }, + { + "name": "Until Capacity Is Available", + "type": "Until", + "dependsOn": [ + { + "activity": "Set Version", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Scopes", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Retention", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@equals(variables('tryAgain'), false)", + "type": "Expression" + }, + "activities": [ + { + "name": "Confirm Ingestion Capacity", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } + "userProperties": [], + "typeProperties": { + "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", + "commandTimeout": "00:20:00" }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" } } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", - "stopTriggers", - "storageAccount" - ] - }, - "approveStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ + { + "name": "If Has Capacity", + "type": "IfCondition", + "dependsOn": [ { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } + "activity": "Confirm Ingestion Capacity", + "dependencyConditions": [ + "Succeeded" + ] } ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "getStoragePrivateEndpointConnections", - "storageAccount" - ] - }, - "stopTriggers": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "arguments": { - "value": "-Stop" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", + "type": "Expression" + }, + "ifFalseActivities": [ { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" + "name": "Wait for Ingestion", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 15 + } }, { - "name": "DataFactoryName", - "value": "[parameters('app').dataFactory]" - } - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" + "name": "Try Again", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait for Ingestion", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "value": { - "type": "string" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": true } } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" + ], + "ifTrueActivities": [ + { + "name": "Set ingestion policy in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "version": { - "type": "string" + "userProperties": [], + "typeProperties": { + "command": { + "value": "[if(variables('useFabric'), format('.show database {0} policy managed_identity', parameters('dataExplorerIngestionDatabase')), format('.alter-merge database {0} policy managed_identity \"[ {{ ''ObjectId'' : ''{1}'', ''AllowedUsages'' : ''NativeIngestion'' }}]\"', parameters('dataExplorerIngestionDatabase'), parameters('dataExplorerPrincipalId')))]", + "type": "Expression" + }, + "commandTimeout": "00:20:00" }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } + } + }, + { + "name": "Save Hub Settings in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Set ingestion policy in ADX", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" + "userProperties": [], + "typeProperties": { + "command": { + "value": "@concat('.append HubSettingsLog <| print version=\"', variables('version'), '\",scopes=dynamic(', variables('scopes'), '),retention=dynamic(', variables('retention'), ') | extend scopes = iff(isnull(scopes[0]), pack_array(scopes), scopes) | mv-apply scopeObj = scopes on (where isnotempty(scopeObj.scope) | summarize scopes = make_set(scopeObj.scope))')", + "type": "Expression" + }, + "commandTimeout": "00:20:00" }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" } } }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + { + "name": "Update PricingUnits in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Save Hub Settings in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace PricingUnits <| externaldata(x_PricingUnitDescription: string, AccountTypes: string, x_PricingBlockSize: decimal, PricingUnit: string)[@\"{0}/PricingUnits.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away AccountTypes', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } + }, + { + "name": "Update Regions in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update PricingUnits in ADX", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace Regions <| externaldata(ResourceLocation: string, RegionId: string, RegionName: string)[@\"{0}/Regions.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" + }, + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" } } }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." + { + "name": "Update ResourceTypes in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update Regions in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace ResourceTypes <| externaldata(x_ResourceType: string, SingularDisplayName: string, PluralDisplayName: string, LowerSingularDisplayName: string, LowerPluralDisplayName: string, IsPreview: bool, Description: string, IconUri: string, Links: string)[@\"{0}/ResourceTypes.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away Links', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" }, - "name": { - "type": "string" + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } } }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" + { + "name": "Update Services in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update ResourceTypes in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "keyVault": { - "type": "string" + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace Services <| externaldata(x_ConsumedService: string, x_ResourceType: string, ServiceName: string, ServiceCategory: string, ServiceSubcategory: string, PublisherName: string, x_PublisherCategory: string, x_Environment: string, x_ServiceModel: string)[@\"{0}/Services.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" }, - "storage": { - "type": "string" + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } + } + }, + { + "name": "Ingestion Complete", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Update Services in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false } } + ] + } + }, + { + "name": "Abort On Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "If Has Capacity", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false + } + } + ], + "timeout": "0.02:00:00" + } + }, + { + "name": "Timeout Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Until Capacity Is Available", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": "Data Explorer ingestion timed out after 2 hours while waiting for available capacity. Please re-run this pipeline to re-attempt ingestion. If you continue to see this error, please report an issue at https://aka.ms/ftk/ideas.", + "errorCode": "DataExplorerIngestionTimeout" + } + } + ], + "concurrency": 1, + "variables": { + "version": { + "type": "String" + }, + "scopes": { + "type": "String" + }, + "retention": { + "type": "String" + }, + "tryAgain": { + "type": "Boolean", + "defaultValue": true + } + } + }, + "dependsOn": [ + "dataset_config", + "linkedService_dataExplorer" + ], + "metadata": { + "description": "Initializes the hub instance based on the configuration settings." + } + }, + "pipeline_StartBackfillProcess": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_StartBackfillProcess', variables('safeConfigContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Config", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('fileName')", + "type": "Expression" + }, + "folderPath": { + "value": "@variables('folderPath')", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set backfill end date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "endDate", + "value": { + "value": "@addDays(startOfMonth(utcNow()), -1)", + "type": "Expression" + } + } + }, + { + "name": "Set backfill start date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "startDate", + "value": { + "value": "@subtractFromTime(startOfMonth(utcNow()), activity('Get Config').output.firstRow.retention.ingestion.months, 'Month')", + "type": "Expression" + } + } + }, + { + "name": "Set export start date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set backfill start date", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "thisMonth", + "value": { + "value": "@startOfMonth(variables('endDate'))", + "type": "Expression" + } + } + }, + { + "name": "Set export end date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set export start date", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "nextMonth", + "value": { + "value": "@startOfMonth(subtractFromTime(variables('thisMonth'), 1, 'Month'))", + "type": "Expression" + } + } + }, + { + "name": "Every Month", + "type": "Until", + "dependsOn": [ + { + "activity": "Set export end date", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set backfill end date", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@less(variables('thisMonth'), variables('startDate'))", + "type": "Expression" + }, + "activities": [ + { + "name": "Update export start date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Backfill data", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "thisMonth", + "value": { + "value": "@variables('nextMonth')", + "type": "Expression" } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } + } + }, + { + "name": "Update export end date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Update export start date", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "nextMonth", + "value": { + "value": "@subtractFromTime(variables('thisMonth'), 1, 'Month')", + "type": "Expression" + } + } + }, + { + "name": "Backfill data", + "type": "ExecutePipeline", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_RunBackfillJob', variables('safeConfigContainerName'))]", + "type": "PipelineReference" }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" + "waitOnCompletion": true, + "parameters": { + "StartDate": { + "value": "@variables('thisMonth')", + "type": "Expression" }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "EndDate": { + "value": "@addDays(addToTime(variables('thisMonth'), 1, 'Month'), -1)", + "type": "Expression" } } + } + } + ], + "timeout": "0.02:00:00" + } + } + ], + "concurrency": 1, + "variables": { + "exportName": { + "type": "String" + }, + "storageAccountId": { + "type": "String", + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + }, + "finOpsHub": { + "type": "String", + "defaultValue": "[parameters('hubName')]" + }, + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" + }, + "fileName": { + "type": "String", + "defaultValue": "settings.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[parameters('configContainerName')]" + }, + "endDate": { + "type": "String" + }, + "startDate": { + "type": "String" + }, + "thisMonth": { + "type": "String" + }, + "nextMonth": { + "type": "String" + } + } + }, + "dependsOn": [ + "dataset_config", + "pipeline_RunBackfillJob" + ], + "metadata": { + "description": "Runs the backfill job for each month based on retention settings." + } + }, + "pipeline_RunBackfillJob": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_RunBackfillJob', variables('safeConfigContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Config", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('fileName')", + "type": "Expression" + }, + "folderPath": { + "value": "@variables('folderPath')", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Scopes", + "description": "Save scopes to test if it is an array", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@activity('Get Config').output.firstRow.scopes", + "type": "Expression" + } + } + }, + { + "name": "Set Scopes as Array", + "description": "Wraps a single scope object into an array to work around the PowerShell bug where single-item arrays are sometimes written as a single object instead of an array.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Scopes", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@createArray(activity('Get Config').output.firstRow.scopes)", + "type": "Expression" + } + } + }, + { + "name": "Filter Invalid Scopes", + "description": "Remove any invalid scopes to avoid errors.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Set Scopes", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Scopes as Array", + "dependencyConditions": [ + "Skipped", + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@variables('scopesArray')", + "type": "Expression" + }, + "condition": { + "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", + "type": "Expression" + } + } + }, + { + "name": "ForEach Export Scope", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Filter Invalid Scopes", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Filter Invalid Scopes').output.Value", + "type": "Expression" + }, + "isSequential": true, + "activities": [ + { + "name": "Set backfill export name", + "type": "SetVariable", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "variableName": "exportName", + "value": { + "value": "@toLower(concat(variables('finOpsHub'), '-monthly-costdetails'))", + "type": "Expression" + } + } + }, + { + "name": "Trigger backfill export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "Set backfill export name", + "dependencyConditions": [ + "Completed" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 1, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}/run?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] + "method": "POST", + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunBackfill@{0}', variables('ftkVersion'))]", + "Content-Type": "application/json", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "body": "{\"timePeriod\" : { \"from\" : \"@{pipeline().parameters.StartDate}\", \"to\" : \"@{pipeline().parameters.EndDate}\" }}", + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } } } } - }, - "dependsOn": [ - "appTelemetry", - "dataFactory", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" ] } - }, - "outputs": { - "dataFactoryId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Factory instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" - }, - "keyVaultId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Key Vault instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" - }, - "storageAccountId": { - "type": "string", - "metadata": { - "description": "Resource ID of the storage account instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - }, - "triggerManagerIdentityName": { - "type": "string", - "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." - }, - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - } } - } - } - }, - "ingestion_OpenDataInternalScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionOpenDataInternal", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", + ], + "concurrency": 1, "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" - }, - "databaseName": { - "value": "[variables('INGESTION_DB')]" - }, - "scripts": { - "value": { - "OpenDataFunctions_resource_type_1": "[variables('$fxv#0')]", - "OpenDataFunctions_resource_type_2": "[variables('$fxv#1')]", - "OpenDataFunctions_resource_type_3": "[variables('$fxv#2')]", - "OpenDataFunctions_resource_type_4": "[variables('$fxv#3')]", - "OpenDataFunctions_resource_type_5": "[variables('$fxv#4')]" - } - }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "StartDate": { + "type": "string" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "EndDate": { + "type": "string" } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" - } - }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." - } - } + "variables": { + "exportName": { + "type": "String" }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" - } - } - ] - } - }, - "dependsOn": [ - "cluster", - "cluster::ingestionDb" - ] - }, - "ingestion_InitScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionInit", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" + "storageAccountId": { + "type": "String", + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" }, - "databaseName": { - "value": "[variables('INGESTION_DB')]" + "finOpsHub": { + "type": "String", + "defaultValue": "[parameters('hubName')]" }, - "scripts": { - "value": { - "openData": "[variables('$fxv#5')]", - "common": "[variables('$fxv#6')]", - "infra": "[variables('$fxv#7')]", - "rawTables": "[replace(variables('$fxv#8'), '$$rawRetentionInDays$$', string(parameters('rawRetentionInDays')))]" - } + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "fileName": { + "type": "String", + "defaultValue": "settings.json" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "folderPath": { + "type": "String", + "defaultValue": "[parameters('configContainerName')]" + }, + "scopesArray": { + "type": "Array" } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" + } + }, + "dependsOn": [ + "dataset_config" + ], + "metadata": { + "description": "Creates and triggers exports for all defined scopes for the specified date range." + } + }, + "pipeline_StartExportProcess": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_StartExportProcess', variables('safeConfigContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Config", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('fileName')", + "type": "Expression" + }, + "folderPath": { + "value": "@variables('folderPath')", + "type": "Expression" + } + } + } } }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + { + "name": "Set Scopes", + "description": "Save scopes to test if it is an array", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@activity('Get Config').output.firstRow.scopes", + "type": "Expression" } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + { + "name": "Set Scopes as Array", + "description": "Wraps a single scope object into an array to work around the PowerShell bug where single-item arrays are sometimes written as a single object instead of an array.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Scopes", + "dependencyConditions": [ + "Failed" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@createArray(activity('Get Config').output.firstRow.scopes)", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "Filter Invalid Scopes", + "description": "Remove any invalid scopes to avoid errors.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Set Scopes", + "dependencyConditions": [ + "Succeeded" + ] }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "activity": "Set Scopes as Array", + "dependencyConditions": [ + "Succeeded", + "Skipped" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@variables('scopesArray')", + "type": "Expression" + }, + "condition": { + "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", + "type": "Expression" } } - ] - } - }, - "dependsOn": [ - "cluster", - "cluster::ingestionDb", - "ingestion_OpenDataInternalScripts" - ] - }, - "ingestion_VersionedScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionVersioned", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", + }, + { + "name": "ForEach Export Scope", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Filter Invalid Scopes", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Filter Invalid Scopes').output.Value", + "type": "Expression" + }, + "isSequential": true, + "activities": [ + { + "name": "Get exports for scope", + "type": "WebActivity", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "GET", + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "Run exports for scope", + "type": "ExecutePipeline", + "dependsOn": [ + { + "activity": "Get exports for scope", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_RunExportJobs', variables('safeConfigContainerName'))]", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "ExportScopes": { + "value": "@activity('Get exports for scope').output.value", + "type": "Expression" + }, + "Recurrence": { + "value": "@pipeline().parameters.Recurrence", + "type": "Expression" + } + } + } + } + ] + } + } + ], + "concurrency": 1, "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" + "Recurrence": { + "type": "string", + "defaultValue": "Daily" + } + }, + "variables": { + "fileName": { + "type": "String", + "defaultValue": "settings.json" }, - "databaseName": { - "value": "[variables('INGESTION_DB')]" + "folderPath": { + "type": "String", + "defaultValue": "[parameters('configContainerName')]" }, - "scripts": { - "value": { - "v1_0": "[variables('$fxv#9')]", - "v1_2": "[variables('$fxv#10')]" - } + "finOpsHub": { + "type": "String", + "defaultValue": "[parameters('hubName')]" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "scopesArray": { + "type": "Array" } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" - } - }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." - } - } - }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" - } - } - ] } }, "dependsOn": [ - "cluster", - "cluster::ingestionDb", - "ingestion_InitScripts" - ] + "dataset_config", + "pipeline_RunExportJobs" + ], + "metadata": { + "description": "Gets a list of all Cost Management exports configured for this hub based on the scopes defined in settings.json, then runs each export using the config_RunExportJobs pipeline." + } }, - "hub_InitScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubInit", + "pipeline_RunExportJobs": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_RunExportJobs', variables('safeConfigContainerName')))]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" - }, - "databaseName": { - "value": "[variables('HUB_DB')]" - }, - "scripts": { - "value": { - "common": "[variables('$fxv#11')]", - "openData": "[variables('$fxv#12')]" + "activities": [ + { + "name": "ForEach export scope", + "type": "ForEach", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@pipeline().parameters.exportScopes", + "type": "Expression" + }, + "isSequential": true, + "activities": [ + { + "name": "If scheduled", + "type": "IfCondition", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@and( startswith(toLower(item().name), toLower(variables('hubName'))), and(contains(string(item().properties.schedule), 'recurrence'), equals(toLower(item().properties.schedule.recurrence), toLower(pipeline().parameters.Recurrence))))", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Trigger export", + "type": "WebActivity", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "method": "POST", + "url": { + "value": "[format('@{{replace(toLower(concat(variables(''resourceManagementUri''),item().id)), ''com//'', ''com/'')}}/run?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "body": " ", + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + } + ] + } + } + ] } + } + ], + "concurrency": 1, + "parameters": { + "ExportScopes": { + "type": "array" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" - }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "Recurrence": { + "type": "string", + "defaultValue": "Daily" } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" - } + "variables": { + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." + "hubName": { + "type": "String", + "defaultValue": "[parameters('hubName')]" + } + } + }, + "dependsOn": [ + "dataset_config" + ], + "metadata": { + "description": "Runs the specified Cost Management exports." + } + }, + "pipeline_ConfigureExports": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ConfigureExports', variables('safeConfigContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Config", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('fileName')", + "type": "Expression" + }, + "folderPath": { + "value": "@variables('folderPath')", + "type": "Expression" + } + } } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + { + "name": "Save Scopes", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@activity('Get Config').output.firstRow.scopes", + "type": "Expression" } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + { + "name": "Save Scopes as Array", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Save Scopes", + "dependencyConditions": [ + "Failed" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@array(activity('Get Config').output.firstRow.scopes)", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "Filter Invalid Scopes", + "type": "Filter", + "dependsOn": [ + { + "activity": "Save Scopes", + "dependencyConditions": [ + "Succeeded" + ] }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "activity": "Save Scopes as Array", + "dependencyConditions": [ + "Skipped", + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@variables('scopesArray')", + "type": "Expression" + }, + "condition": { + "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", + "type": "Expression" + } + } + }, + { + "name": "ForEach Export Scope", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Filter Invalid Scopes", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Filter Invalid Scopes').output.value", + "type": "Expression" + }, + "isSequential": true, + "activities": [ + { + "name": "Set Export Type", + "type": "SetVariable", + "dependsOn": [], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "exportScopeType", + "value": { + "value": "@if(contains(toLower(item().scope), 'providers/microsoft.billing/billingaccounts'), if(contains(toLower(item().scope), ':'), 'mca', 'ea'), if(contains(toLower(item().scope), 'subscriptions/'), 'subscription', 'undefined'))", + "type": "Expression" + } + } + }, + { + "name": "Switch Export Type", + "type": "Switch", + "dependsOn": [ + { + "activity": "Set Export Type", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "on": { + "value": "@toLower(variables('exportScopeType'))", + "type": "Expression" + }, + "cases": [ + { + "value": "ea", + "activities": [ + { + "name": "EA open month focus export", + "type": "WebActivity", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "EA closed month focus export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA open month focus export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "EA monthly pricesheet export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA closed month focus export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'Pricesheet', variables('exportSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "Trigger EA monthly pricesheet export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA monthly pricesheet export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "method": "POST", + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}/run?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "body": " ", + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "EA daily reservation details export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA monthly pricesheet export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationDetails', variables('reservationDetailsSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationDetails@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "EA daily reservation transactions export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA daily reservation details export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationtransactions''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationTransactions', variables('exportSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationTransactions@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "EA daily shared 30day virtualmachines", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA daily reservation transactions export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-recommendations-shared-last30days-virtualmachines''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationRecommendations', variables('exportSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', 'Shared', 'Last30Days', 'VirtualMachines')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationRecommendations.VM.Shared.30d@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + } + ] + }, + { + "value": "subscription", + "activities": [ + { + "name": "Subscription open month focus export", + "type": "WebActivity", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "Subscription closed month focus export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "Subscription open month focus export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + } + ] + }, + { + "value": "mca", + "activities": [ + { + "name": "Export Type Unsupported Error", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('MCA agreements are not supported for managed exports :',variables('exportScope'))", + "type": "Expression" + }, + "errorCode": "ExportTypeUnsupported" + } + } + ] + } + ], + "defaultActivities": [ + { + "name": "Export Type Not Defined Error", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to determine the export scope type for :',variables('exportScope'))", + "type": "Expression" + }, + "errorCode": "ExportTypeNotDefined" + } + } + ] + } + } + ] } - ] - } - }, - "dependsOn": [ - "cluster", - "cluster::hubDb", - "ingestion_InitScripts" - ] - }, - "hub_VersionedScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubVersioned", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" + } + ], + "concurrency": 1, + "variables": { + "scopesArray": { + "type": "Array" }, - "databaseName": { - "value": "[variables('HUB_DB')]" + "exportName": { + "type": "String" }, - "scripts": { - "value": { - "v1_0": "[variables('$fxv#13')]", - "v1_2": "[variables('$fxv#14')]" - } + "exportScope": { + "type": "String" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "exportScopeType": { + "type": "String" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "storageAccountId": { + "type": "String", + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + }, + "finOpsHub": { + "type": "String", + "defaultValue": "[parameters('hubName')]" + }, + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" + }, + "fileName": { + "type": "String", + "defaultValue": "settings.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[parameters('configContainerName')]" } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" + } + }, + "dependsOn": [ + "dataset_config" + ], + "metadata": { + "description": "Creates Cost Management exports for supported scopes." + } + }, + "pipeline_ExecuteExportsETL": { + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ExecuteETL', variables('safeExportContainerName')))]", + "properties": { + "activities": [ + { + "name": "Wait", + "description": "Files may not be available immediately after being created.", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 60 } }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." + { + "name": "Read Manifest", + "description": "Load the export manifest to determine the scope, dataset, and date range.", + "type": "Lookup", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Completed" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@pipeline().parameters.fileName", + "type": "Expression" + }, + "folderPath": { + "value": "@pipeline().parameters.folderPath", + "type": "Expression" + } + } } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + { + "name": "Set Has No Rows", + "description": "Check the row count ", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + "userProperties": [], + "typeProperties": { + "variableName": "hasNoRows", + "value": { + "value": "@or(equals(activity('Read Manifest').output.firstRow.blobCount, null), equals(activity('Read Manifest').output.firstRow.blobCount, 0))", + "type": "Expression" + } + } + }, + { + "name": "Set Export Dataset Type", + "description": "Save the dataset type from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "userProperties": [], + "typeProperties": { + "variableName": "exportDatasetType", + "value": { + "value": "@activity('Read Manifest').output.firstRow.exportConfig.type", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "name": "Set MCA Column", + "description": "Determines if the dataset schema has channel-specific columns and saves the column name that only exists in MCA to determine if it is an MCA dataset.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "mcaColumnToCheck", + "value": { + "value": "@if(contains(createArray('pricesheet', 'reservationtransactions'), toLower(variables('exportDatasetType'))), 'BillingProfileId', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Net Savings', null))", + "type": "Expression" } } - ] - } - }, - "dependsOn": [ - "cluster", - "cluster::hubDb", - "hub_InitScripts", - "ingestion_VersionedScripts" - ] - }, - "hub_LatestScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubLatest", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" - }, - "databaseName": { - "value": "[variables('HUB_DB')]" }, - "scripts": { - "value": { - "latest": "[variables('$fxv#15')]" + { + "name": "Set Export Dataset Version", + "description": "Save the dataset version from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "exportDatasetVersion", + "value": { + "value": "@activity('Read Manifest').output.firstRow.exportConfig.dataVersion", + "type": "Expression" + } } }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" - }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" + { + "name": "Detect Channel", + "description": "Determines what channel this export is from. Switch statement handles the different file types if the mcaColumnToCheck variable is set.", + "type": "Switch", + "dependsOn": [ + { + "activity": "Set Has No Rows", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set MCA Column", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Export Dataset Version", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "on": { + "value": "@if(or(empty(variables('mcaColumnToCheck')), variables('hasNoRows')), 'ignore', last(array(split(activity('Read Manifest').output.firstRow.blobs[0].blobName, '.'))))", + "type": "Expression" + }, + "cases": [ + { + "value": "csv", + "activities": [ + { + "name": "Check for MCA Column in CSV", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeExportContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel in CSV", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in CSV", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in CSV').output, 'firstRow'), contains(activity('Check for MCA Column in CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + }, + { + "value": "gz", + "activities": [ + { + "name": "Check for MCA Column in Gzip CSV", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "dataset": { + "referenceName": "[format('{0}_gzip', variables('safeExportContainerName'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel in Gzip CSV", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in Gzip CSV", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Gzip CSV').output, 'firstRow'), contains(activity('Check for MCA Column in Gzip CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + }, + { + "value": "parquet", + "activities": [ + { + "name": "Check for MCA Column in Parquet", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "ParquetSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + }, + "dataset": { + "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel for Parquet", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in Parquet", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Parquet').output, 'firstRow'), contains(activity('Check for MCA Column in Parquet').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + } + ], + "defaultActivities": [ + { + "name": "Set Schema File", + "type": "SetVariable", + "dependsOn": [], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), '.json'))", + "type": "Expression" + } + } + } + ] } }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." + { + "name": "Set Scope", + "description": "Save the scope from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + "userProperties": [], + "typeProperties": { + "variableName": "scope", + "value": { + "value": "@split(toLower(activity('Read Manifest').output.firstRow.exportConfig.resourceId), '/providers/microsoft.costmanagement/exports/')[0]", + "type": "Expression" } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + { + "name": "Set Date", + "description": "Save the exported month from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + "userProperties": [], + "typeProperties": { + "variableName": "date", + "value": { + "value": "@replace(substring(activity('Read Manifest').output.firstRow.runInfo.startDate, 0, 7), '-', '')", + "type": "Expression" } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + } + }, + { + "name": "Failed to Read Manifest", + "type": "Fail", + "dependsOn": [ + { + "activity": "Set Date", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Scope", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Export Dataset Version", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Detect Channel", + "dependencyConditions": [ + "Failed" + ] } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Failed to read the manifest file for this export run. Manifest path: ', pipeline().parameters.folderPath)", + "type": "Expression" + }, + "errorCode": "ManifestReadFailed" } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "Check Schema", + "description": "Verify that the schema file exists in storage.", + "type": "GetMetadata", + "dependsOn": [ + { + "activity": "Set Scope", + "dependencyConditions": [ + "Succeeded" + ] }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "activity": "Set Date", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Detect Channel", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('schemaFile')", + "type": "Expression" + }, + "folderPath": "[format('{0}/schemas', parameters('configContainerName'))]" + } + }, + "fieldList": [ + "exists" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" } } - ] - } - }, - "dependsOn": [ - "cluster", - "cluster::hubDb", - "hub_VersionedScripts" - ] - }, - "getDataExplorerPrivateEndpointConnections": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetDataExplorerPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataExplorerName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "18030646605933559953" + }, + { + "name": "Schema Not Found", + "type": "Fail", + "dependsOn": [ + { + "activity": "Check Schema", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('The ', variables('schemaFile'), ' schema mapping file was not found. Please confirm version ', variables('exportDatasetVersion'), ' of the ', variables('exportDatasetType'), ' dataset is supported by this version of FinOps hubs. You may need to upgrade to a newer release. To add support for another dataset, you can create a custom mapping file.')", + "type": "Expression" + }, + "errorCode": "SchemaNotFound" } }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + { + "name": "Set Hub Dataset", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "dataExplorerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the ADX cluster." + "userProperties": [], + "typeProperties": { + "variableName": "hubDataset", + "value": { + "value": "@if(equals(toLower(variables('exportDatasetType')), 'focuscost'), 'Costs', if(equals(toLower(variables('exportDatasetType')), 'pricesheet'), 'Prices', if(equals(toLower(variables('exportDatasetType')), 'reservationdetails'), 'CommitmentDiscountUsage', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Recommendations', if(equals(toLower(variables('exportDatasetType')), 'reservationtransactions'), 'Transactions', if(equals(toLower(variables('exportDatasetType')), 'actualcost'), 'ActualCosts', if(equals(toLower(variables('exportDatasetType')), 'amortizedcost'), 'AmortizedCosts', toLower(variables('exportDatasetType')))))))))", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + { + "name": "Set Destination Folder", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check Schema", + "dependencyConditions": [ + "Succeeded" + ] }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Kusto/clusters/privateEndpointConnections", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } + { + "activity": "Set Hub Dataset", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "destinationFolder", + "value": { + "value": "@replace(concat(variables('hubDataset'),'/',substring(variables('date'), 0, 4),'/',substring(variables('date'), 4, 2),'/',toLower(variables('scope')), if(equals(variables('hubDataset'), 'Recommendations'), activity('Read Manifest').output.firstRow.exportConfig.exportName, '')),'//','/')", + "type": "Expression" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "cluster", - "dataFactoryVNet::dataExplorerManagedPrivateEndpoint" - ] - }, - "approveDataExplorerPrivateEndpointConnections": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveDataExplorerPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataExplorerName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" }, - "privateEndpointConnections": { - "value": "[reference('getDataExplorerPrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "18030646605933559953" + { + "name": "For Each Blob", + "description": "Loop thru each exported file listed in the manifest.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Set Destination Folder", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(variables('hasNoRows'), json('[]'), activity('Read Manifest').output.firstRow.blobs)", + "type": "Expression" + }, + "batchCount": "[if(parameters('enablePublicAccess'), 30, 4)]", + "isSequential": false, + "activities": [ + { + "name": "Execute", + "description": "Run the ingestion ETL pipeline.", + "type": "ExecutePipeline", + "dependsOn": [], + "policy": { + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_ETL_{1}', variables('safeExportContainerName'), variables('safeIngestionContainerName'))]", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "blobPath": { + "value": "@item().blobName", + "type": "Expression" + }, + "destinationFolder": { + "value": "@variables('destinationFolder')", + "type": "Expression" + }, + "destinationFile": { + "value": "@last(array(split(replace(replace(item().blobName, '.gz', ''), '.csv', '.parquet'), '/')))", + "type": "Expression" + }, + "ingestionId": { + "value": "@activity('Read Manifest').output.firstRow.runInfo.runId", + "type": "Expression" + }, + "schemaFile": { + "value": "@variables('schemaFile')", + "type": "Expression" + }, + "exportDatasetType": { + "value": "@variables('exportDatasetType')", + "type": "Expression" + }, + "exportDatasetVersion": { + "value": "@variables('exportDatasetVersion')", + "type": "Expression" + } + } + } + } + ] } }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + { + "name": "Copy Manifest", + "description": "Copy the manifest to the ingestion container to trigger ADX ingestion", + "type": "Copy", + "dependsOn": [ + { + "activity": "For Each Blob", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "dataExplorerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the ADX cluster." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Kusto/clusters/privateEndpointConnections", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" + "sink": { + "type": "JsonSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "JsonWriteSettings" + } + }, + "enableStaging": false + }, + "inputs": [ + { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": "manifest.json", + "folderPath": { + "value": "@pipeline().parameters.folderPath", + "type": "Expression" + } } } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" - } + ], + "outputs": [ + { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": "manifest.json", + "folderPath": { + "value": "[format('@concat(''{0}/'', variables(''destinationFolder''))', parameters('ingestionContainerName'))]", + "type": "Expression" + } + } + } + ] + } + ], + "parameters": { + "folderPath": { + "type": "string" + }, + "fileName": { + "type": "string" } - } - }, - "dependsOn": [ - "cluster", - "getDataExplorerPrivateEndpointConnections" - ] - }, - "trigger_IngestionManifestAdded": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core_IngestionManifestAddedTrigger", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('app').dataFactory]" + "variables": { + "date": { + "type": "String" }, - "triggerName": { - "value": "[format('{0}_ManifestAdded', variables('INGESTION'))]" + "destinationFolder": { + "type": "String" }, - "pipelineName": { - "value": "[format('{0}_ExecuteETL', variables('INGESTION'))]" + "exportDatasetType": { + "type": "String" }, - "pipelineParameters": { - "value": { - "folderPath": "@triggerBody().folderPath" - } + "exportDatasetVersion": { + "type": "String" }, - "storageAccountName": { - "value": "[parameters('app').storage]" + "hasNoRows": { + "type": "Boolean" }, - "storageContainer": { - "value": "[variables('INGESTION')]" + "hubDataset": { + "type": "String" }, - "storagePathEndsWith": { - "value": "manifest.json" + "mcaColumnToCheck": { + "type": "String" + }, + "schemaFile": { + "type": "String" + }, + "scope": { + "type": "String" } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "14264521107451792604" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } + "annotations": [ + "New export" + ] + }, + "dependsOn": [ + "dataset_config", + "dataset_manifest", + "dataset_msexports", + "dataset_msexports_gzip", + "dataset_msexports_parquet", + "pipeline_ToIngestion" + ], + "metadata": { + "description": "Queues the msexports_ETL_ingestion pipeline." + } + }, + "pipeline_ToIngestion": { + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ETL_{1}', variables('safeExportContainerName'), variables('safeIngestionContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Existing Parquet Files", + "description": "Get the previously ingested files so we can remove any older data. This is necessary to avoid data duplication in reports.", + "type": "GetMetadata", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[format('{0}_files', variables('safeIngestionContainerName'))]", + "type": "DatasetReference", + "parameters": { + "folderPath": "@pipeline().parameters.destinationFolder" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" } - }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + { + "name": "Filter Out Current Exports", + "description": "Remove existing files from the current export so those files do not get deleted.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Get Existing Parquet Files", + "dependencyConditions": [ + "Completed" + ] } - }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", + "type": "Expression" + }, + "condition": { + "value": "[format('@and(endswith(item().name, ''.parquet''), not(startswith(item().name, concat(pipeline().parameters.ingestionId, ''{0}''))))', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" } + } + }, + { + "name": "Load Schema Mappings", + "description": "Get schema mapping file to use for the CSV to parquet conversion.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@toLower(pipeline().parameters.schemaFile)", + "type": "Expression" + }, + "folderPath": "[format('{0}/schemas', parameters('configContainerName'))]" + } } - }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } + }, + { + "name": "Failed to Load Schema", + "type": "Fail", + "dependsOn": [ + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Failed" + ] } - }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to load the ', pipeline().parameters.schemaFile, ' schema file. Please confirm the schema and version are supported for FinOps hubs ingestion. Unsupported files will remain in the msexports container.')", + "type": "Expression" + }, + "errorCode": "SchemaLoadFailed" + } + }, + { + "name": "Set Additional Columns", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + "userProperties": [], + "typeProperties": { + "variableName": "additionalColumns", + "value": { + "value": "@intersection(array(json(concat('[{\"name\":\"x_SourceProvider\",\"value\":\"Microsoft\"},{\"name\":\"x_SourceName\",\"value\":\"Cost Management\"},{\"name\":\"x_SourceType\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"},{\"name\":\"x_SourceVersion\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"}'))), activity('Load Schema Mappings').output.firstRow.additionalColumns)", + "type": "Expression" } } }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" + { + "name": "For Each Old File", + "description": "Loop thru each of the existing files from previous exports.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Convert to Parquet", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Filter Out Current Exports", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Filter Out Current Exports').output.Value", + "type": "Expression" + }, + "activities": [ + { + "name": "Delete Old Ingested File", + "description": "Delete the previously ingested files from older exports.", + "type": "Delete", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[variables('safeIngestionContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@concat(pipeline().parameters.destinationFolder, '/', item().name)", + "type": "Expression" + } + } }, - "parameters": "[parameters('pipelineParameters')]" + "enableLogging": false, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + } } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] } - } + ] } - ] - } - }, - "dependsOn": [ - "appRegistration", - "pipeline_ExecuteIngestionETL" - ] - }, - "runInitializationPipeline": { - "condition": "[or(variables('useAzure'), variables('useFabric'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_InitializeHub", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "dataFactoryInstances": { - "value": [ - "[parameters('app').dataFactory]" - ] - }, - "identityName": { - "value": "[reference('appRegistration').outputs.triggerManagerIdentityName.value]" }, - "startPipelines": { - "value": [ - "[format('{0}_InitializeHub', variables('CONFIG'))]" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4749940909471549408" + { + "name": "Set Destination Path", + "type": "SetVariable", + "dependsOn": [], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "destinationPath", + "value": { + "value": "[format('@concat(pipeline().parameters.destinationFolder, ''/'', pipeline().parameters.ingestionId, ''{0}'', pipeline().parameters.destinationFile)', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" + } } }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" + { + "name": "Convert to Parquet", + "description": "[format('Convert CSV to parquet and move the file to the {0} container.', parameters('ingestionContainerName'))]", + "type": "Switch", + "dependsOn": [ + { + "activity": "Set Destination Path", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Additional Columns", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "on": { + "value": "@last(array(split(pipeline().parameters.blobPath, '.')))", + "type": "Expression" + }, + "cases": [ + { + "value": "csv", + "activities": [ + { + "name": "Convert CSV File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:10:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false, + "translator": { + "value": "@activity('Load Schema Mappings').output.firstRow.translator", + "type": "Expression" + } + }, + "inputs": [ + { + "referenceName": "[variables('safeExportContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('safeIngestionContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" + ] }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + { + "value": "gz", + "activities": [ + { + "name": "Convert GZip CSV File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:10:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false, + "translator": { + "value": "@activity('Load Schema Mappings').output.firstRow.translator", + "type": "Expression" + } + }, + "inputs": [ + { + "referenceName": "[format('{0}_gzip', variables('safeExportContainerName'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('safeIngestionContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] } - } + ] }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" + { + "value": "parquet", + "activities": [ + { + "name": "Move Parquet File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "ParquetSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false + }, + "inputs": [ + { + "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('safeIngestionContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] } - } + ] } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + ], + "defaultActivities": [ + { + "name": "Unsupported File Type", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to ingest the specified export file because the file type is not supported. File: ', pipeline().parameters.blobPath)", + "type": "Expression" + }, + "errorCode": "UnsupportedExportFileType" + } } - } + ] + } + }, + { + "name": "Read Hub Config", + "description": "Read the hub config to determine if the export should be retained.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false }, - "name": { - "type": "string" + "formatSettings": { + "type": "JsonReadSettings" } }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": "settings.json", + "folderPath": "[parameters('configContainerName')]" } } + } + }, + { + "name": "If Not Retaining Exports", + "description": "If the msexports retention period <= 0, delete the source file. The main reason to keep the source file is to allow for troubleshooting and reprocessing in the future.", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Convert to Parquet", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Read Hub Config", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@lessOrEquals(coalesce(activity('Read Hub Config').output.firstRow.retention.msexports.days, 0), 0)", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Delete Source File", + "description": "Delete the exported data file to keep storage costs down. This file is not referenced by any reporting systems.", + "type": "Delete", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + }, + "enableLogging": false, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + } + } + } + ] + } + } + ], + "parameters": { + "blobPath": { + "type": "String" + }, + "destinationFile": { + "type": "string" + }, + "destinationFolder": { + "type": "string" + }, + "ingestionId": { + "type": "string" + }, + "schemaFile": { + "type": "string" + }, + "exportDatasetType": { + "type": "string" + }, + "exportDatasetVersion": { + "type": "string" + } + }, + "variables": { + "additionalColumns": { + "type": "Array" + }, + "destinationPath": { + "type": "String" + } + }, + "annotations": [] + }, + "dependsOn": [ + "dataset_config", + "dataset_ingestion", + "dataset_ingestion_files", + "dataset_msexports", + "dataset_msexports_gzip", + "dataset_msexports_parquet" + ], + "metadata": { + "description": "Transforms CSV data to a standard schema and converts to Parquet." + } + }, + "pipeline_ToDataExplorer": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ETL_dataExplorer', variables('safeIngestionContainerName')))]", + "properties": { + "activities": [ + { + "name": "Read Hub Config", + "description": "Read the hub config to determine how long data should be retained.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "formatSettings": { + "type": "JsonReadSettings" } }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": "settings.json", + "folderPath": "[parameters('configContainerName')]" } } } }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "dataFactoryInstances": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. List of Azure Data Factory instances to start triggers for. Can be up to 1 per publisher." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to use when starting the triggers." + { + "name": "Set Final Retention Months", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Hub Config", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "startAllTriggers": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Start all triggers for the Data Factory instances. Default: false." + "userProperties": [], + "typeProperties": { + "variableName": "finalRetentionMonths", + "value": { + "value": "@coalesce(activity('Read Hub Config').output.firstRow.retention.final.months, 999)", + "type": "Expression" } - }, - "startPipelines": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. List of pipelines to run. Default: [] (no pipelines)." + } + }, + { + "name": "Until Capacity Is Available", + "type": "Until", + "dependsOn": [ + { + "activity": "Set Final Retention Months", + "dependencyConditions": [ + "Completed", + "Skipped" + ] } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "uniqueInstances": "[union(filter(parameters('dataFactoryInstances'), lambda('adf', not(empty(lambdaVariables('adf'))))), createArray())]" - }, - "resources": { - "initialize": { - "copy": { - "name": "initialize", - "count": "[length(variables('uniqueInstances'))]" + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@equals(variables('tryAgain'), false)", + "type": "Expression" }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[if(lessOrEquals(length(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()])), 64), format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), substring(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), 0, 64))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[parameters('identityName')]" + "activities": [ + { + "name": "Confirm Ingestion Capacity", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "scriptContent": { - "value": "[variables('$fxv#0')]" + "userProperties": [], + "typeProperties": { + "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", + "commandTimeout": "00:20:00" }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[variables('uniqueInstances')[copyIndex()]]" - }, - { - "name": "Pipelines", - "value": "[join(parameters('startPipelines'), '|')]" - }, - { - "name": "StartAllTriggers", - "value": "[string(parameters('startAllTriggers'))]" - } - ] + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference" } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + { + "name": "If Has Capacity", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Confirm Ingestion Capacity", + "dependencyConditions": [ + "Succeeded" + ] } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", + "type": "Expression" }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } + "ifFalseActivities": [ + { + "name": "Wait for Ingestion", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 15 } }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." + { + "name": "Try Again", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait for Ingestion", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": true } } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" + ], + "ifTrueActivities": [ + { + "name": "Pre-Ingest Cleanup", + "description": "Cost Management exports include all month-to-date data from the previous export run. To ensure data is not double-reported, it must be dropped from the raw table before ingestion completes. Remove previous ingestions into the raw table for the month and any previous runs of the current ingestion month file in any table.", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "scriptStorage": { - "type": "string" + "typeProperties": { + "command": { + "value": "@concat('.drop extents <| .show extents | where (TableName == \"', pipeline().parameters.table, '\" and Tags !has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '\") or (Tags has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '/', pipeline().parameters.originalFileName, '\")')", + "type": "Expression" + }, + "commandTimeout": "00:20:00" }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } + } + }, + { + "name": "Ingest Data", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Pre-Ingest Cleanup", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 3, + "retryIntervalInSeconds": 120, + "secureOutput": false, + "secureInput": false }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } + "userProperties": [], + "typeProperties": { + "command": { + "value": "[format('@concat(''.ingest into table '', pipeline().parameters.table, '' (\"abfss://{0}@{1}.dfs.{2}/'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.fileName, '';{3}\") with (format=\"parquet\", ingestionMappingReference=\"'', pipeline().parameters.table, ''_mapping\", tags=\"[\\\"drop-by:'', pipeline().parameters.ingestionId, ''\\\", \\\"drop-by:'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.originalFileName, ''\\\", \\\"drop-by:ftk-version-{4}\\\"]\"); print Success = assert(iff(toscalar($command_results | project-keep HasErrors) == false, true, false), \"Ingestion Failed\")'')', parameters('ingestionContainerName'), parameters('storageAccountName'), environment().suffixes.storage, if(variables('useFabric'), 'impersonate', 'managed_identity=system'), variables('ftkVersion'))]", + "type": "Expression" + }, + "commandTimeout": "01:00:00" + }, + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" } } }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." + { + "name": "Post-Ingest Cleanup", + "description": "Cost Management exports include all month-to-date data from the previous export run. To ensure data is not double-reported, it must be dropped after ingestion completes. Remove the current ingestion month file from raw and any old ingestions for the month from the final table.", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Ingest Data", + "dependencyConditions": [ + "Completed" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." + "typeProperties": { + "command": { + "value": "@concat('.drop extents <| .show extents | extend isOldFinalData = (TableName startswith \"', replace(pipeline().parameters.table, '_raw', '_final_v'), '\" and Tags !has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '\") | extend isPastFinalRetention = (TableName startswith \"', replace(pipeline().parameters.table, '_raw', '_final_v'), '\" and todatetime(substring(strcat(replace_string(extract(\"drop-by:[A-Za-z]+/(\\\\d{4}/\\\\d{2}(/\\\\d{2})?)\", 1, Tags), \"/\", \"-\"), \"-01\"), 0, 10)) < datetime_add(\"month\", -', if(lessOrEquals(variables('finalRetentionMonths'), 0), 0, variables('finalRetentionMonths')), ', startofmonth(now()))) | where isOldFinalData or isPastFinalRetention')", + "type": "Expression" + }, + "commandTimeout": "00:20:00" }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" + }, + { + "name": "Ingestion Complete", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Post-Ingest Cleanup", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "name": { - "type": "string" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false } }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" + { + "name": "Abort On Ingestion Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Ingest Data", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false } }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + { + "name": "Ingestion Failed Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Abort On Ingestion Error", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Data Explorer ingestion into the ', pipeline().parameters.table, ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Ingest Data').output.errors), 0), activity('Ingest Data').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Ingest Data').output.errors), 0), activity('Ingest Data').output.errors[0].Code, 'None'), ')')", + "type": "Expression" + }, + "errorCode": "DataExplorerIngestionFailed" + } + }, + { + "name": "Abort On Pre-Ingest Drop Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Pre-Ingest Cleanup", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false + } + }, + { + "name": "Pre-Ingest Drop Failed Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Abort On Pre-Ingest Drop Error", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Data Explorer pre-ingestion cleanup (drop extents from raw table) for the ', pipeline().parameters.table, ' table failed. Ingestion was not completed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", + "type": "Expression" + }, + "errorCode": "DataExplorerPreIngestionDropFailed" } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + { + "name": "Abort On Post-Ingest Drop Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Post-Ingest Cleanup", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false + } + }, + { + "name": "Post-Ingest Drop Failed Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Abort On Post-Ingest Drop Error", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Data Explorer post-ingestion cleanup (drop extents from final tables) for the ', replace(pipeline().parameters.table, '_raw', '_final_*'), ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", + "type": "Expression" + }, + "errorCode": "DataExplorerPostIngestionDropFailed" + } } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + ] + } + } + ], + "timeout": "0.02:00:00" + } + } + ], + "parameters": { + "folderPath": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "originalFileName": { + "type": "string" + }, + "ingestionId": { + "type": "string" + }, + "table": { + "type": "string" + } + }, + "variables": { + "tryAgain": { + "type": "Boolean", + "defaultValue": true + }, + "logRetentionDays": { + "type": "Integer", + "defaultValue": 0 + }, + "finalRetentionMonths": { + "type": "Integer", + "defaultValue": 999 + } + }, + "annotations": [] + }, + "dependsOn": [ + "dataset_config", + "linkedService_dataExplorer" + ], + "metadata": { + "description": "Ingests parquet data into an Azure Data Explorer cluster." + } + }, + "pipeline_ExecuteIngestionETL": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ExecuteETL', variables('safeIngestionContainerName')))]", + "properties": { + "concurrency": 1, + "activities": [ + { + "name": "Wait", + "description": "Files may not be available immediately after being created.", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 60 + } + }, + { + "name": "Set Container Folder Path", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "containerFolderPath", + "value": { + "value": "@join(skip(array(split(pipeline().parameters.folderPath, '/')), 1), '/')", + "type": "Expression" + } + } + }, + { + "name": "Get Existing Parquet Files", + "description": "Get the previously ingested files so we can get file paths.", + "type": "GetMetadata", + "dependsOn": [ + { + "activity": "Set Container Folder Path", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[format('{0}_files', variables('safeIngestionContainerName'))]", + "type": "DatasetReference", + "parameters": { + "folderPath": "@variables('containerFolderPath')" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + } + }, + { + "name": "Filter Out Folders", + "description": "Remove any folders or manifest files.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Get Existing Parquet Files", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", + "type": "Expression" + }, + "condition": { + "value": "@and(equals(item().type, 'File'), not(contains(toLower(item().name), 'manifest.json')))", + "type": "Expression" + } + } + }, + { + "name": "Set Ingestion Timestamp", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "timestamp", + "value": { + "value": "@utcNow()", + "type": "Expression" + } + } + }, + { + "name": "For Each Old File", + "description": "Loop thru each of the existing files.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Filter Out Folders", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Data Explorer validation", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "batchCount": "[parameters('dataExplorerIngestionCapacity')]", + "items": { + "value": "@activity('Filter Out Folders').output.Value", + "type": "Expression" + }, + "activities": [ + { + "name": "Execute", + "description": "Run the ADX ETL pipeline.", + "type": "ExecutePipeline", + "dependsOn": [], + "policy": { + "secureInput": false }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_ETL_dataExplorer', variables('safeIngestionContainerName'))]", + "type": "PipelineReference" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" + "waitOnCompletion": true, + "parameters": { + "folderPath": { + "value": "@variables('containerFolderPath')", + "type": "Expression" }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" + "fileName": { + "value": "@item().name", + "type": "Expression" }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } + "originalFileName": { + "value": "[format('@last(array(split(item().name, ''{0}'')))', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "ingestionId": { + "value": "[format('@concat(first(array(split(item().name, ''{0}''))), ''_'', variables(''timestamp''))', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" + }, + "table": { + "value": "@concat(first(array(split(variables('containerFolderPath'), '/'))), '_raw')", + "type": "Expression" + } } } } + ] + } + }, + { + "name": "If No Files", + "description": "If there are no files found, fail the pipeline.", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Filter Out Folders", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@equals(length(activity('Filter Out Folders').output.Value), 0)", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Files Not Found", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to locate parquet files to ingest from the ', pipeline().parameters.folderPath, ' path. Please confirm the folder path is the full path, including the \"ingestion\" container and not starting with or ending with a slash (\"/\").')", + "type": "Expression" + }, + "errorCode": "IngestionFilesNotFound" + } + } + ] } - } - } - }, - "dependsOn": [ - "appRegistration", - "pipeline_InitializeHub" - ] - } - }, - "outputs": { - "clusterId": { - "type": "string", - "metadata": { - "description": "The resource ID of the cluster." - }, - "value": "[if(variables('useFabric'), '', resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-')))]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "The ID of the cluster system assigned managed identity." - }, - "value": "[if(variables('useFabric'), '', reference('cluster', '2023-08-15', 'full').identity.principalId)]" - }, - "clusterName": { - "type": "string", - "metadata": { - "description": "The name of the cluster." - }, - "value": "[if(variables('useFabric'), '', replace(parameters('clusterName'), '_', '-'))]" - }, - "clusterUri": { - "type": "string", - "metadata": { - "description": "The URI of the cluster." - }, - "value": "[variables('dataExplorerUri')]" - }, - "ingestionDbName": { - "type": "string", - "metadata": { - "description": "The name of the database for data ingestion." - }, - "value": "[variables('INGESTION_DB')]" - }, - "hubDbName": { - "type": "string", - "metadata": { - "description": "The name of the database for queries." - }, - "value": "[variables('HUB_DB')]" - }, - "clusterIngestionCapacity": { - "type": "int", - "metadata": { - "description": "Max ingestion capacity of the cluster." - }, - "value": "[variables('dataExplorerIngestionCapacity')]" - } - } - } - }, - "dependsOn": [ - "cmExports", - "core", - "deleteOldResources" - ] - }, - "remoteHub": { - "condition": "[not(empty(parameters('remoteHubStorageKey')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.RemoteHub", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'RemoteHub')]" - }, - "remoteStorageKey": { - "value": "[parameters('remoteHubStorageKey')]" - }, - "remoteHubStorageUri": { - "value": "[parameters('remoteHubStorageUri')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "3199707033377872229" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" + }, + { + "name": "Data Explorer validation", + "description": "If Data Explorer is stopped, start it", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Set Ingestion Timestamp", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "[format('@equals({0}, true)', variables('deployDataExplorer'))]", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Start ADX Cluster", + "type": "WebActivity", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "method": "POST", + "url": { + "value": "[format('{0}{1}/start?api-version=2024-04-13', environment().resourceManager, resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')))]", + "type": "Expression" + }, + "body": "{}", + "authentication": { + "type": "MSI", + "resource": { + "value": "[environment().resourceManager]", + "type": "Expression" + } + } + } + }, + { + "name": "Error ADX Start", + "type": "Fail", + "dependsOn": [ + { + "activity": "Start ADX Cluster After Error", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Failed to start the Data Explorer instance. Message: ', activity('Start ADX Cluster After Error').output.error.message)", + "type": "Expression" + }, + "errorCode": { + "value": "@activity('Start ADX Cluster After Error').output.error.code", + "type": "Expression" + } + } + }, + { + "name": "Wait ADX Provision State", + "type": "Wait", + "dependsOn": [ + { + "activity": "Start ADX Cluster", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 600 + } + }, + { + "name": "Start ADX Cluster After Error", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "Wait ADX Provision State", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "method": "POST", + "url": { + "value": "[format('{0}{1}/start?api-version=2024-04-13', environment().resourceManager, resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')))]", + "type": "Expression", + "body": "{}" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "[environment().resourceManager]", + "type": "Expression" + } + } + } + } + ] } } + ], + "parameters": { + "folderPath": { + "type": "string" + } }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } + "variables": { + "containerFolderPath": { + "type": "string" + }, + "timestamp": { + "type": "string" } - } + }, + "annotations": [ + "New ingestion" + ] }, + "dependsOn": [ + "dataset_ingestion_files", + "pipeline_ToDataExplorer" + ], "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } + "description": "Queues the ingestion_ETL_dataExplorer pipeline to account for Data Factory pipeline trigger limits." } }, - "_1.HubRoutingProperties": { - "type": "object", + "azuretimezones": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "azuretimezones", "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "scriptStorage": { - "type": "string" + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + } }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "4022825617953122148" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." + } }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "timezoneobject": { + "type": "object", + "defaultValue": { + "australiaeast": "AUS Eastern Standard Time", + "australiacentral": "AUS Eastern Standard Time", + "australiacentral2": "AUS Eastern Standard Time", + "australiasoutheast": "AUS Eastern Standard Time", + "brazilsouth": "E. South America Standard Time", + "canadacentral": "Central Standard Time", + "canadaeast": "Eastern Standard Time", + "centralindia": "India Standard Time", + "centralus": "Central Standard Time", + "eastasia": "China Standard Time", + "eastus": "Eastern Standard Time", + "eastus2": "Eastern Standard Time", + "francecentral": "W. Europe Standard Time", + "germanynorth": "W. Europe Standard Time", + "germanywestcentral": "W. Europe Standard Time", + "japaneast": "Japan Standard Time", + "japanwest": "Japan Standard Time", + "koreacentral": "Korea Standard Time", + "koreasouth": "Korea Standard Time", + "northcentralus": "Central Standard Time", + "northeurope": "GMT Standard Time", + "norwayeast": "W. Europe Standard Time", + "norwaywest": "W. Europe Standard Time", + "southcentralus": "Central Standard Time", + "southindia": "India Standard Time", + "southeastasia": "Singapore Standard Time", + "switzerlandnorth": "W. Europe Standard Time", + "switzerlandwest": "W. Europe Standard Time", + "uksouth": "GMT Standard Time", + "ukwest": "GMT Standard Time", + "westcentralus": "Central Standard Time", + "westeurope": "W. Europe Standard Time", + "westindia": "India Standard Time", + "westus": "Pacific Standard Time", + "westus2": "Pacific Standard Time" + } }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "utchrs": { + "type": "string", + "defaultValue": "[utcNow('hh')]" }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "utcmins": { + "type": "string", + "defaultValue": "[utcNow('mm')]" + }, + "utcsecs": { + "type": "string", + "defaultValue": "[utcNow('ss')]" } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" + }, + "variables": { + "loc": "[toLower(replace(parameters('location'), ' ', ''))]", + "timezone": "[coalesce(tryGet(parameters('timezoneobject'), variables('loc')), 'Universal Coordinated Time')]" + }, + "resources": [], + "outputs": { + "AzureRegion": { + "type": "string", + "value": "[parameters('location')]" }, - "dataFactory": { - "type": "string" + "Timezone": { + "type": "string", + "value": "[variables('timezone')]" }, - "keyVault": { - "type": "string" + "UtcHours": { + "type": "string", + "value": "[parameters('utchrs')]" }, - "scripts": { - "type": "string" + "UtcMinutes": { + "type": "string", + "value": "[parameters('utcmins')]" }, - "storage": { - "type": "string" + "UtcSeconds": { + "type": "string", + "value": "[parameters('utcsecs')]" } } } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } } }, - "HubAppProperties": { - "type": "object", + "getStoragePrivateEndpointConnections": { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "GetStoragePrivateEndpointConnections", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "storage": { - "type": "string" + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + } }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "privateRoutingForLinkedServices": { - "parameters": [ + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "491732910990436410" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ { - "$ref": "#/definitions/_1.HubProperties", - "name": "hub" + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } } ], - "output": { - "type": "object", - "value": "[if(parameters('hub').options.privateRouting, createObject('connectVia', createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference')), createObject())]" - }, - "metadata": { - "description": "Returns an object that represents the properties needed to enable private routing for linked services. Use property expansion (`...value`) to apply to a linkedServices resource.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" } } } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "remoteStorageKey": { - "type": "securestring", - "metadata": { - "description": "Required. Create and store a key for a remote storage account." - } - }, - "remoteHubStorageUri": { - "type": "string", - "metadata": { - "description": "Required. Remote storage account for ingestion dataset." - } - }, - "ingestionContainerName": { - "type": "string", - "defaultValue": "ingestion", - "metadata": { - "description": "Optional. Name of the ingestion container. Default: ingestion." - } - } - }, - "variables": { - "storageKeySecretName": "[format('{0}-storage-key', toLower(parameters('app').hub.name))]", - "finOpsToolkitVersion": "12.0" - }, - "resources": { - "dataFactory::linkedService_remoteHubStorage": { - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'remoteHubStorage')]", - "properties": "[shallowMerge(createArray(createObject('annotations', createArray(), 'parameters', createObject(), 'type', 'AzureBlobFS', 'typeProperties', createObject('url', parameters('remoteHubStorageUri'), 'accountKey', createObject('type', 'AzureKeyVaultSecret', 'store', createObject('referenceName', parameters('app').keyVault, 'type', 'LinkedServiceReference'), 'secretName', variables('storageKeySecretName')))), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]" - }, - "dataFactory::dataset_ingestion": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('ingestionContainerName'))]", - "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" - }, - "fileSystem": "[parameters('ingestionContainerName')]" - } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "remoteHubStorage", - "type": "LinkedServiceReference" - } }, "dependsOn": [ - "dataFactory::linkedService_remoteHubStorage" + "storageManagedPrivateEndpoint" ] }, - "dataFactory::dataset_ingestion_files": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', parameters('ingestionContainerName')))]", + "approveStoragePrivateEndpointConnections": { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ApproveStoragePrivateEndpointConnections", "properties": { - "annotations": [], + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", "parameters": { - "folderPath": { - "type": "String" + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" } }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileSystem": "[parameters('ingestionContainerName')]", - "folderPath": { - "value": "@dataset().folderPath", - "type": "Expression" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "491732910990436410" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" } } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "remoteHubStorage", - "type": "LinkedServiceReference" } }, "dependsOn": [ - "dataFactory::linkedService_remoteHubStorage" + "getStoragePrivateEndpointConnections" ] }, - "keyVault": { - "existing": true, - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]" - }, - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]" - }, - "appRegistration": { + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.RemoteHub_Register", + "apiVersion": "2022-09-01", + "name": "GetKeyVaultPrivateEndpointConnections", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "version": { - "value": "[variables('finOpsToolkitVersion')]" - }, - "features": { - "value": [ - "DataFactory", - "KeyVault", - "Storage" - ] + "keyVaultName": { + "value": "[parameters('keyVaultName')]" } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5436870138046688593" + "version": "0.36.177.2456", + "templateHash": "11127712826844297340" } }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, + "keyVaultName": { + "type": "string", "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the KeyVault." } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" } } - }, - "HubAppFeature": { - "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "keyVaultManagedPrivateEndpoint" + ] + }, + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "11127712826844297340" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, + "keyVaultName": { + "type": "string", "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the KeyVault." } } }, - "functions": [ + "resources": [ { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" } } } ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections" + ] + }, + "getDataExplorerPrivateEndpointConnections": { + "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "GetDataExplorerPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataExplorerName": { + "value": "[parameters('dataExplorerName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9394304748737938982" + } + }, "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], "metadata": { - "description": "Required. FinOps hub app getting deployed." + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "version": { + "dataExplorerName": { "type": "string", "metadata": { - "description": "Required. Version number of the FinOps hub app." + "description": "Required. Name of the ADX cluster." } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } } - }, - "storageRoles": { + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "dataExplorerManagedPrivateEndpoint" + ] + }, + "approveDataExplorerPrivateEndpointConnections": { + "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ApproveDataExplorerPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataExplorerName": { + "value": "[parameters('dataExplorerName')]" + }, + "privateEndpointConnections": { + "value": "[reference('getDataExplorerPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9394304748737938982" + } + }, + "parameters": { + "privateEndpointConnections": { "type": "array", - "items": { - "type": "string" - }, "defaultValue": [], "metadata": { - "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "telemetryString": { + "dataExplorerName": { "type": "string", - "defaultValue": "", "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + "description": "Required. Name of the ADX cluster." } } }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0}', parameters('app').id)]", - "version": "[parameters('version')]" - } - }, - "resources": [] + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } } - }, - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", - "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getDataExplorerPrivateEndpointConnections" + ] + }, + "deleteOldResources": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_ADF.DeleteOldResources", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" }, - "resources": { - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", - "properties": { - "name": "[parameters('app').storage]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] + "identityName": { + "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "storageAccount" - ] - }, - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", - "properties": { - "name": "[parameters('app').keyVault]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "keyVault" - ] - }, - "dataFactory::managedVirtualNetwork": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "properties": {}, - "dependsOn": [ - "dataFactory" - ] - }, - "dataFactory::managedIntegrationRuntime": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + { + "name": "DataFactoryName", + "value": "[parameters('dataFactoryName')]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "588615643779078900" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "default", - "type": "ManagedVirtualNetworkReference" + "name": { + "type": "string" }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('app').hub.location]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 - } - } + "value": { + "type": "string" } - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedVirtualNetwork" - ] + } }, - "dataFactory::linkedService_keyVault": { - "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "_1.HubProperties": { + "type": "object", "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + "id": { + "type": "string" }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "keyVault" - ] - }, - "dataFactory::linkedService_storageAccount": { - "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + "name": { + "type": "string" }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "storageAccount" - ] - }, - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] - }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] - }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] - }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] - }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] - }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] - }, - "appTelemetry": { - "condition": "[parameters('app').hub.options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", - "properties": "[variables('telemetryProps')]" - }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" + } }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } }, - "storageRoleAssignments": { - "copy": { - "name": "storageRoleAssignments", - "count": "[length(variables('factoryStorageRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "storageAccount" - ] - }, - "triggerManagerIdentity": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "dependsOn": [ - "dataFactory" - ] - }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "triggerManagerIdentity" - ] - }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "location": "[parameters('app').hub.location]", - "sku": { - "name": "[parameters('app').hub.options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" - }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "_1.HubRoutingProperties": { + "type": "object", "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" + "networkId": { + "type": "string" }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "blob" - ] + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } } - ] + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } }, - "dependsOn": [ - "storageAccount" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "_1.IdNameObject": { + "type": "object", "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" + "id": { + "type": "string" }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] + "name": { + "type": "string" + } }, - "dependsOn": [ - "storageAccount" - ] + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "HubAppProperties": { + "type": "object", "properties": { - "sku": { - "name": "[parameters('app').hub.options.keyVaultSku]", - "family": "A" + "name": { + "type": "string" }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" } } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" } }, - "dependsOn": [ - "dataFactory" - ] + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('app').keyVault)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.keyVault]" - }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" }, - "dependsOn": [ - "keyVault" - ] + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetKeyVaultPrivateEndpointConnections", + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" }, "dependsOn": [ - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", - "getStoragePrivateEndpointConnections", - "keyVault" + "identity" ] }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} } }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", "dependsOn": [ - "getKeyVaultPrivateEndpointConnections", - "keyVault" + "identity", + "identityRoleAssignments" ] - }, - "getStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } + } + } + } + }, + "dependsOn": [ + "stopTriggers", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" + ] + }, + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_ADF.StopTriggers", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" + }, + "scriptContent": { + "value": "[variables('$fxv#1')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('dataFactoryName')]" + }, + { + "name": "Triggers", + "value": "[join(variables('allHubTriggers'), '|')]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "588615643779078900" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", - "stopTriggers", - "storageAccount" - ] + } }, - "approveStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveStoragePrivateEndpointConnections", + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } + "name": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } - } - }, - "dependsOn": [ - "getStoragePrivateEndpointConnections", - "storageAccount" - ] - }, - "stopTriggers": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "arguments": { - "value": "-Stop" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('app').dataFactory]" - } - ] - } + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "queue": { + "$ref": "#/definitions/_1.IdNameObject" }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "table": { + "$ref": "#/definitions/_1.IdNameObject" } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } + "keyVault": { + "type": "string" }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } + "displayName": { + "type": "string" }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } + "suffix": { + "type": "string" }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } + "tags": { + "type": "object" } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } + } + }, + "dependsOn": [ + "triggerManagerIdentity", + "triggerManagerRoleAssignments" + ] + }, + "trigger_ExportManifestAdded": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_ExportManifestAddedTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('dataFactoryName')]" + }, + "triggerName": { + "value": "[variables('exportManifestAddedTriggerName')]" + }, + "pipelineName": { + "value": "[format('{0}_ExecuteETL', variables('safeExportContainerName'))]" + }, + "pipelineParameters": { + "value": { + "folderPath": "@triggerBody().folderPath", + "fileName": "@triggerBody().fileName" + } + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "storageContainer": { + "value": "[parameters('exportContainerName')]" + }, + "storagePathEndsWith": { + "value": "manifest.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "10717799137710795976" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." + } + }, + "triggerName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory trigger to create or update." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storageContainer": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storagePathStartsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." + } + }, + "storagePathEndsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } + }, + "pipelineName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } + }, + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + } + } + }, + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] + "parameters": "[parameters('pipelineParameters')]" + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] + } + } + } + ] + } + }, + "dependsOn": [ + "pipeline_ExecuteExportsETL", + "stopTriggers" + ] + }, + "trigger_IngestionManifestAdded": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_IngestionManifestAddedTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('dataFactoryName')]" + }, + "triggerName": { + "value": "[variables('ingestionManifestAddedTriggerName')]" + }, + "pipelineName": { + "value": "[format('{0}_ExecuteETL', variables('safeIngestionContainerName'))]" + }, + "pipelineParameters": { + "value": { + "folderPath": "@triggerBody().folderPath" + } + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "storageContainer": { + "value": "[parameters('ingestionContainerName')]" + }, + "storagePathEndsWith": { + "value": "manifest.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "10717799137710795976" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." + } + }, + "triggerName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory trigger to create or update." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storageContainer": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storagePathStartsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." + } + }, + "storagePathEndsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } + }, + "pipelineName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } + }, + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + } + } + }, + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } + "parameters": "[parameters('pipelineParameters')]" } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] } - }, - "dependsOn": [ - "appTelemetry", - "dataFactory", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" - ] + } } + ] + } + }, + "dependsOn": [ + "pipeline_ExecuteIngestionETL", + "stopTriggers" + ] + }, + "trigger_SettingsUpdated": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_SettingsUpdatedTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('dataFactoryName')]" }, - "outputs": { - "dataFactoryId": { + "triggerName": { + "value": "[variables('updateConfigTriggerName')]" + }, + "pipelineName": { + "value": "[format('{0}_ConfigureExports', variables('safeConfigContainerName'))]" + }, + "pipelineParameters": { + "value": {} + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "storageContainer": { + "value": "[parameters('configContainerName')]" + }, + "storagePathEndsWith": { + "value": "settings.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "10717799137710795976" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." + } + }, + "triggerName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory trigger to create or update." + } + }, + "storageAccountName": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Resource ID of the Data Factory instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } }, - "keyVaultId": { + "storageContainer": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Resource ID of the Key Vault instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } }, - "storageAccountId": { + "storagePathStartsWith": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Resource ID of the storage account instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." + } }, - "principalId": { + "storagePathEndsWith": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } }, - "triggerManagerIdentityName": { + "pipelineName": { "type": "string", "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." - }, - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } + }, + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + } } - } + }, + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" + }, + "parameters": "[parameters('pipelineParameters')]" + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] + } + } + } + ] } - } + }, + "dependsOn": [ + "pipeline_ConfigureExports", + "stopTriggers" + ] }, - "keyVault_secret": { + "startTriggers": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "keyVault_secret", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_ADF.StartTriggers", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "vaultName": { - "value": "[parameters('app').keyVault]" - }, - "secretName": { - "value": "[variables('storageKeySecretName')]" + "app": { + "value": "[parameters('app')]" }, - "secretValue": { - "value": "[parameters('remoteStorageKey')]" + "identityName": { + "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" }, - "secretExpirationInSeconds": { - "value": 1702648632 + "scriptContent": { + "value": "[variables('$fxv#2')]" }, - "secretNotBeforeInSeconds": { - "value": 10000 + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('dataFactoryName')]" + }, + { + "name": "Triggers", + "value": "[join(variables('allHubTriggers'), '|')]" + }, + { + "name": "Pipelines", + "value": "[join(createArray(format('{0}_InitializeHub', variables('safeConfigContainerName'))), '|')]" + } + ] } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8808837526156050188" + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } }, - "parameters": { - "vaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Key Vault instance." + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } } }, - "secretName": { - "type": "string", + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Required. Name of the Key Vault secret to create or update." + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "secretValue": { - "type": "securestring", + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Required. Value of the Key Vault secret." + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "secretExpirationInSeconds": { - "type": "int", - "defaultValue": -1, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, "metadata": { - "description": "Optional. Value of the Key Vault secret expiration date (exp) property. This is represented as seconds since Jan 1, 1970." + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "secretNotBeforeInSeconds": { - "type": "int", - "defaultValue": -1, - "metadata": { - "description": "Optional. Value of the Key Vault secret not before date (nbf) property. This is represented as seconds since Jan 1, 1970." - } - } - }, - "resources": [ - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('vaultName'), parameters('secretName'))]", + "HubAppProperties": { + "type": "object", "properties": { - "attributes": "[union(createObject('enabled', true()), if(lessOrEquals(parameters('secretExpirationInSeconds'), 0), createObject(), createObject('exp', parameters('secretExpirationInSeconds'))), if(lessOrEquals(parameters('secretNotBeforeInSeconds'), 0), createObject(), createObject('nbf', parameters('secretNotBeforeInSeconds'))))]", - "value": "[parameters('secretValue')]" - } - } - ], - "outputs": { - "secretName": { - "type": "string", - "metadata": { - "description": "Name of the Key Vault secret." - }, - "value": "[parameters('secretName')]" - } - } - } - } - } - }, - "outputs": { - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Name of the Key Vault instance." - }, - "value": "[parameters('app').keyVault]" - } - } - } - }, - "dependsOn": [ - "core" - ] - }, - "deleteOldResources": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.DeleteOldResources", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[reference('core').outputs.app.value]" - }, - "identityName": { - "value": "[reference('core').outputs.triggerManagerIdentityName.value]" - }, - "scriptContent": { - "value": "[variables('$fxv#1')]" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[reference('core').outputs.app.value.dataFactory]" - } - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } }, - "dataFactory": { - "type": "string" + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } }, - "keyVault": { - "type": "string" + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" }, - "scripts": { - "type": "string" + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } + } }, "dependsOn": [ - "identity" + "deleteOldResources", + "pipeline_InitializeHub", + "trigger_DailySchedule", + "trigger_ExportManifestAdded", + "trigger_IngestionManifestAdded", + "trigger_MonthlySchedule", + "trigger_SettingsUpdated", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The Resource ID of the Data factory." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName'))]" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } + "name": { + "type": "string", + "metadata": { + "description": "The Name of the Azure Data Factory instance." }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "value": "[parameters('dataFactoryName')]" } } } }, "dependsOn": [ - "core" + "cmExports", + "core", + "dataExplorer", + "remoteHub" ] }, - "startTriggers": { + "remoteHub": { + "condition": "[not(empty(parameters('remoteHubStorageKey')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.StartTriggers", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.RemoteHub", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "app": { - "value": "[reference('core').outputs.app.value]" - }, - "dataFactoryInstances": { - "value": [ - "[reference('core').outputs.app.value.dataFactory]", - "[reference('cmExports').outputs.app.value.dataFactory]" - ] - }, - "identityName": { - "value": "[reference('core').outputs.triggerManagerIdentityName.value]" + "hub": { + "value": "[variables('hub')]" }, - "startAllTriggers": { - "value": true + "remoteStorageKey": { + "value": "[parameters('remoteHubStorageKey')]" } }, "template": { @@ -23950,97 +18064,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4749940909471549408" + "version": "0.36.177.2456", + "templateHash": "3708155483370559900" } }, "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, "_1.HubRoutingProperties": { "type": "object", "properties": { @@ -24073,9 +18101,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -24102,7 +18127,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -24133,7 +18157,7 @@ } } }, - "HubAppProperties": { + "HubProperties": { "type": "object", "properties": { "id": { @@ -24142,39 +18166,78 @@ "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "location": { "type": "string" }, "tags": { "type": "object" }, - "dataFactory": { - "type": "string" + "tagsByResource": { + "type": "object" }, - "keyVault": { + "version": { "type": "string" }, - "storage": { - "type": "string" + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" } @@ -24182,95 +18245,55 @@ } }, "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "dataFactoryInstances": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. List of Azure Data Factory instances to start triggers for. Can be up to 1 per publisher." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to use when starting the triggers." - } - }, - "startAllTriggers": { - "type": "bool", - "defaultValue": false, + "hub": { + "$ref": "#/definitions/HubProperties", "metadata": { - "description": "Optional. Start all triggers for the Data Factory instances. Default: false." + "description": "Required. FinOps hub instance properties." } }, - "startPipelines": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], + "remoteStorageKey": { + "type": "securestring", "metadata": { - "description": "Optional. List of pipelines to run. Default: [] (no pipelines)." + "description": "Required. Create and store a key for a remote storage account." } } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "uniqueInstances": "[union(filter(parameters('dataFactoryInstances'), lambda('adf', not(empty(lambdaVariables('adf'))))), createArray())]" + "$fxv#0": "12.0" }, "resources": { - "initialize": { - "copy": { - "name": "initialize", - "count": "[length(variables('uniqueInstances'))]" - }, + "appRegistration": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[if(lessOrEquals(length(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()])), 64), format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), substring(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), 0, 64))]", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.RemoteHub_Register", "properties": { "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[parameters('identityName')]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[variables('uniqueInstances')[copyIndex()]]" - }, - { - "name": "Pipelines", - "value": "[join(parameters('startPipelines'), '|')]" - }, - { - "name": "StartAllTriggers", - "value": "[string(parameters('startAllTriggers'))]" - } + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "hub": { + "value": "[parameters('hub')]" + }, + "publisher": { + "value": "Microsoft FinOps hubs" + }, + "namespace": { + "value": "Microsoft.FinOpsHubs" + }, + "appName": { + "value": "RemoteHub" + }, + "displayName": { + "value": "FinOps hub remote relay" + }, + "appVersion": { + "value": "[variables('$fxv#0')]" + }, + "features": { + "value": [ + "KeyVault", + "Storage" ] } }, @@ -24281,361 +18304,932 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "version": "0.36.177.2456", + "templateHash": "15179190433979236138" + } + }, + "definitions": { + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + }, + { + "type": "bool", + "nullable": true, + "name": "forceAppTags" + } + ], + "output": { + "type": "object", + "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "value": { - "type": "string" + "getPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "newApp": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" + }, + { + "type": "string", + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "appPartialName" + }, + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" + } + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" + }, + "metadata": { + "description": "Creates a new FinOps hub app configuration object.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } } }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" + { + "namespace": "_1", + "members": { + "newAppInternal": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" }, - "keyVaultSku": { - "type": "string" + { + "type": "string", + "name": "publisherName" }, - "networkAddressPrefix": { - "type": "string" + { + "type": "string", + "name": "publisherDisplayName" }, - "privateRouting": { - "type": "bool" + { + "type": "string", + "name": "publisherSuffix" }, - "publisherIsolation": { - "type": "bool" + { + "type": "object", + "name": "publisherTags" }, - "storageInfrastructureEncryption": { - "type": "bool" + { + "type": "string", + "name": "appName" }, - "storageSku": { - "type": "string" + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" + } + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": { + "name": "[parameters('appName')]", + "displayName": "[parameters('appDisplayName')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", + "publisher": { + "name": "[parameters('publisherName')]", + "displayName": "[parameters('publisherDisplayName')]", + "suffix": "[parameters('publisherSuffix')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" + }, + "hub": "[parameters('hub')]", + "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "safeStorageName": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "string", + "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "Required. FinOps hub instance properties." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app publisher." + } + }, + "namespace": { + "type": "string", + "metadata": { + "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "appName": { + "type": "string", + "metadata": { + "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app." + } + }, + "appVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Version number of the FinOps hub app." + } + }, + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." + } + }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + } + }, + "variables": { + "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", + "version": "[parameters('appVersion')]" + } + }, + "resources": [] + } + }, + "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" + }, + "resources": { + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', variables('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" } } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" + ] + }, + "dependsOn": [ + "dfsEndpoint" + ] + }, + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] } } - } + ] }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('hub').options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[variables('app').dataFactory]", + "location": "[variables('app').hub.location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" } } }, - "_1.HubRoutingProperties": { - "type": "object", + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[variables('app').storage]", + "location": "[parameters('hub').location]", + "sku": { + "name": "[parameters('hub').options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" + }, + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + }, + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" + "subnet": { + "id": "[parameters('hub').routing.subnets.storage]" }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", + "groupIds": [ + "blob" + ] } } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('hub').routing.subnets.storage]" }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", + "groupIds": [ + "dfs" + ] } } - } + ] }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dependsOn": [ + "storageAccount" + ] }, - "_1.IdNameObject": { - "type": "object", + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[variables('app').keyVault]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", "properties": { - "id": { - "type": "string" + "sku": { + "name": "[parameters('hub').options.keyVaultSku]", + "family": "A" }, - "name": { - "type": "string" + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" } }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dependsOn": [ + "dataFactory" + ] }, - "HubAppProperties": { - "type": "object", + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', variables('app').keyVault)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" + "subnet": { + "id": "[parameters('hub').routing.subnets.keyVault]" }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } + ] }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dependsOn": [ + "keyVault" + ] } }, - "parameters": { + "outputs": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } + "description": "FinOps hub app configuration." + }, + "value": "[variables('app')]" }, - "identityName": { + "principalId": { "type": "string", "metadata": { - "description": "Required. Name of the managed identity to create." + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + } + } + } + } + }, + "keyVault_secret": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "keyVault_secret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vaultName": { + "value": "[reference('appRegistration').outputs.app.value.keyVault]" + }, + "secretName": { + "value": "[format('{0}-storage-key', toLower(reference('appRegistration').outputs.app.value.hub.name))]" + }, + "secretValue": { + "value": "[parameters('remoteStorageKey')]" + }, + "secretExpirationInSeconds": { + "value": 1702648632 + }, + "secretNotBeforeInSeconds": { + "value": 10000 + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "338893459125049689" + } + }, + "parameters": { + "vaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Key Vault instance." } }, - "scriptName": { + "secretName": { "type": "string", - "defaultValue": "[deployment().name]", "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + "description": "Required. Name of the Key Vault secret to create or update." } }, - "scriptContent": { - "type": "string", + "secretValue": { + "type": "securestring", "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Required. Value of the Key Vault secret." } }, - "arguments": { - "type": "string", - "defaultValue": "", + "secretExpirationInSeconds": { + "type": "int", + "defaultValue": -1, "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." + "description": "Optional. Value of the Key Vault secret expiration date (exp) property. This is represented as seconds since Jan 1, 1970." } }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], + "secretNotBeforeInSeconds": { + "type": "int", + "defaultValue": -1, "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "description": "Optional. Value of the Key Vault secret not before date (nbf) property. This is represented as seconds since Jan 1, 1970." } } }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('vaultName'), parameters('secretName'))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } + "attributes": "[union(createObject('enabled', true()), if(lessOrEquals(parameters('secretExpirationInSeconds'), 0), createObject(), createObject('exp', parameters('secretExpirationInSeconds'))), if(lessOrEquals(parameters('secretNotBeforeInSeconds'), 0), createObject(), createObject('nbf', parameters('secretNotBeforeInSeconds'))))]", + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "secretName": { + "type": "string", + "metadata": { + "description": "Name of the Key Vault secret." }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "value": "[parameters('secretName')]" } } } - } + }, + "dependsOn": [ + "appRegistration" + ] + } + }, + "outputs": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Name of the Key Vault instance." + }, + "value": "[reference('appRegistration').outputs.app.value.keyVault]" } } } - }, - "dependsOn": [ - "cmExports", - "core" - ] + } } }, "outputs": { @@ -24686,28 +19280,28 @@ "metadata": { "description": "The resource ID of the Data Explorer cluster." }, - "value": "[if(not(variables('useAzureDataExplorer')), '', reference('analytics').outputs.clusterId.value)]" + "value": "[if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.clusterId.value)]" }, "clusterUri": { "type": "string", "metadata": { "description": "The URI of the Data Explorer cluster." }, - "value": "[if(variables('useFabric'), parameters('fabricQueryUri'), if(not(variables('useAzureDataExplorer')), '', reference('analytics').outputs.clusterUri.value))]" + "value": "[if(variables('useFabric'), parameters('fabricQueryUri'), if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.clusterUri.value))]" }, "ingestionDbName": { "type": "string", "metadata": { "description": "The name of the Data Explorer database used for ingesting data." }, - "value": "[if(or(variables('useFabric'), variables('useAzureDataExplorer')), reference('analytics').outputs.ingestionDbName.value, '')]" + "value": "[if(variables('useFabric'), 'Ingestion', if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.ingestionDbName.value))]" }, "hubDbName": { "type": "string", "metadata": { "description": "The name of the Data Explorer database used for querying data." }, - "value": "[if(or(variables('useFabric'), variables('useAzureDataExplorer')), reference('analytics').outputs.hubDbName.value, '')]" + "value": "[if(variables('useFabric'), 'Hub', if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.hubDbName.value))]" }, "managedIdentityId": { "type": "string", @@ -24748,70 +19342,70 @@ "metadata": { "description": "Name of the Data Factory instance." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.dataFactoryName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.dataFactoryName.value]" }, "storageAccountId": { "type": "string", "metadata": { "description": "Resource ID of the deployed storage account." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageAccountId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageAccountId.value]" }, "storageAccountName": { "type": "string", "metadata": { "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageAccountName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageAccountName.value]" }, "storageUrlForPowerBI": { "type": "string", "metadata": { "description": "URL to use when connecting custom Power BI reports to your data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageUrlForPowerBI.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageUrlForPowerBI.value]" }, "clusterId": { "type": "string", "metadata": { "description": "Resource ID of the Data Explorer cluster." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.clusterId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.clusterId.value]" }, "clusterUri": { "type": "string", "metadata": { "description": "URI of the Data Explorer cluster." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.clusterUri.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.clusterUri.value]" }, "ingestionDbName": { "type": "string", "metadata": { "description": "Name of the Data Explorer database used for ingesting data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.ingestionDbName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.ingestionDbName.value]" }, "hubDbName": { "type": "string", "metadata": { "description": "Name of the Data Explorer database used for querying data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.hubDbName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.hubDbName.value]" }, "managedIdentityId": { "type": "string", "metadata": { "description": "Object ID of the Data Factory managed identity. This will be needed when configuring managed exports." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.managedIdentityId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.managedIdentityId.value]" }, "managedIdentityTenantId": { "type": "string", "metadata": { "description": "Azure AD tenant ID. This will be needed when configuring managed exports." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.managedIdentityTenantId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.managedIdentityTenantId.value]" } } } \ No newline at end of file diff --git a/docs/deploy/finops-hub-12.0.ui.json b/docs/deploy/finops-hub-12.0.ui.json index 201aefd47..16b32e2ef 100644 --- a/docs/deploy/finops-hub-12.0.ui.json +++ b/docs/deploy/finops-hub-12.0.ui.json @@ -696,51 +696,6 @@ } ], "visible": true - }, - { - "name": "remoteHub", - "type": "Microsoft.Common.Section", - "label": "Remote hub configuration", - "elements": [ - { - "name": "remoteHubIntro", - "type": "Microsoft.Common.TextBlock", - "visible": true, - "options": { - "text": "Configure this hub to send data to a remote FinOps hub in another tenant or subscription. This enables cross-tenant cost management scenarios where a central tenant collects cost data from multiple tenants. Leave these fields empty if this is not a remote hub setup." - } - }, - { - "name": "remoteHubStorageUri", - "type": "Microsoft.Common.TextBox", - "label": "Remote hub storage URI", - "toolTip": "Data Lake storage endpoint from the remote hub storage account. Copy from the storage account Settings > Endpoints > Data Lake storage. Example: https://myremotehub.dfs.core.windows.net/", - "constraints": { - "required": false, - "regex": "^$|^https://.*\\.dfs\\.core\\.windows\\.net/?$", - "validationMessage": "Must be a valid Data Lake storage endpoint URL in the format: https://storageaccount.dfs.core.windows.net/" - }, - "visible": true - }, - { - "name": "remoteHubStorageKey", - "type": "Microsoft.Common.PasswordBox", - "label": { - "password": "Remote hub storage key" - }, - "toolTip": "Storage account access key for the remote hub. Copy from the remote hub storage account Security + networking > Access keys > key1/2 > Key.", - "constraints": { - "required": false, - "regex": "^$|^[A-Za-z0-9+/]{86}==$", - "validationMessage": "Must be a valid storage account access key (base64 encoded, ending with ==)" - }, - "options": { - "hideConfirmation": true - }, - "visible": true - } - ], - "visible": true } ] }, @@ -784,8 +739,6 @@ "ingestionRetentionInMonths": "[steps('retention').storage.ingestionMonths]", "dataExplorerRawRetentionInDays": "[steps('retention').dataExplorer.rawDays]", "dataExplorerFinalRetentionInMonths": "[steps('retention').dataExplorer.finalMonths]", - "remoteHubStorageUri": "[steps('advanced').remoteHub.remoteHubStorageUri]", - "remoteHubStorageKey": "[steps('advanced').remoteHub.remoteHubStorageKey]", "tagsByResource": "[steps('tags').tagsByResource]" } } diff --git a/docs/deploy/finops-hub-latest.json b/docs/deploy/finops-hub-latest.json index 3a65ba896..4dd0a7f94 100644 --- a/docs/deploy/finops-hub-latest.json +++ b/docs/deploy/finops-hub-latest.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8677558940311187779" + "version": "0.36.177.2456", + "templateHash": "1039455044893847676" } }, "parameters": { @@ -233,7 +233,7 @@ "resources": [ { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "hub", "properties": { "expressionEvaluationOptions": { @@ -312,29 +312,43 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "13528846700335310209" + "version": "0.36.177.2456", + "templateHash": "7621563314037220493" } }, "definitions": { "_1.HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -343,24 +357,25 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -446,7 +461,7 @@ "apps": {}, "description": "FinOps hub instance properties.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -482,9 +497,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -511,7 +523,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -519,7 +530,7 @@ }, "description": "FinOps hub private network routing properties.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -538,7 +549,7 @@ "name": "Resource name.", "description": "Resource ID and name.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } } @@ -560,7 +571,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -584,7 +595,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -596,38 +607,54 @@ }, { "type": "string", - "name": "id" + "name": "publisherName" }, { "type": "string", - "name": "name" + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherSuffix" + }, + { + "type": "object", + "name": "publisherTags" }, { "type": "string", - "name": "publisher" + "name": "appName" }, { "type": "string", - "name": "suffix" + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" } ], "output": { "$ref": "#/definitions/_1.HubAppProperties", "value": { - "id": "[parameters('id')]", - "name": "[parameters('name')]", - "publisher": "[parameters('publisher')]", - "suffix": "[parameters('suffix')]", - "tags": "[union(parameters('hub').tags, createObject('ftk-hubapp-publisher', parameters('publisher')))]", + "name": "[parameters('appName')]", + "displayName": "[parameters('appDisplayName')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", + "publisher": { + "name": "[parameters('publisherName')]", + "displayName": "[parameters('publisherDisplayName')]", + "suffix": "[parameters('publisherSuffix')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" + }, "hub": "[parameters('hub')]", - "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('suffix'))), 1)), parameters('suffix')), '--', '-')]", - "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('suffix'))), 1)), parameters('suffix')), '--', '-')]", - "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('suffix')))), parameters('suffix'))]" + "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" } }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -715,7 +742,6 @@ "table": "[if(parameters('enablePublicAccess'), createObject('id', '', 'name', ''), _1.dnsZoneIdName('table'))]" }, "subnets": { - "dataExplorer": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'dataExplorer-subnet'))]", "dataFactory": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'private-endpoint-subnet'))]", "keyVault": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'private-endpoint-subnet'))]", "scripts": "[if(parameters('enablePublicAccess'), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('networkName'), 'script-subnet'))]", @@ -729,7 +755,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -746,7 +772,7 @@ }, "metadata": { "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } } @@ -773,7 +799,7 @@ "metadata": { "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -785,21 +811,33 @@ }, { "type": "string", - "name": "publisher" + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "appPartialName" }, { "type": "string", - "name": "app" + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" } ], "output": { "$ref": "#/definitions/_1.HubAppProperties", - "value": "[_1.newAppInternal(parameters('hub'), format('{0}.{1}', parameters('publisher'), parameters('app')), parameters('app'), parameters('publisher'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisher'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisher'))))]" + "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" }, "metadata": { "description": "Creates a new FinOps hub app configuration object.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -853,7 +891,7 @@ "metadata": { "description": "Creates a new FinOps hub configuration object.", "__bicep_imported_from!": { - "sourceTemplate": "fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } } @@ -1102,12 +1140,12 @@ }, "variables": { "$fxv#0": "12.0", - "$fxv#1": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\n#\n$adfParams = @{\n ResourceGroupName = $env:DataFactoryResourceGroup\n DataFactoryName = $env:DataFactoryName\n}\n\n# Delete old triggers\n$triggers = Get-AzDataFactoryV2Trigger @adfParams -ErrorAction SilentlyContinue `\n| Where-Object { $_.Name -match '^msexports(_(setup|daily|monthly|extract|FileAdded))?$' }\n$DeploymentScriptOutputs[\"stopTriggers\"] = $triggers | Stop-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\n$DeploymentScriptOutputs[\"deleteTriggers\"] = $triggers | Remove-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\n\n# Delete old pipelines\n$DeploymentScriptOutputs[\"pipelines\"] = Get-AzDataFactoryV2Pipeline @adfParams -ErrorAction SilentlyContinue `\n| Where-Object { $_.Name -match '^(msexports_(backfill|extract|fill|get|run|setup|transform)|config_(BackfillData|ExportData|RunBackfill|RunExports))$' } `\n| Remove-AzDataFactoryV2Pipeline -Force -ErrorAction SilentlyContinue\n", + "$fxv#1": "12.0", "hub": "[__bicep.newHub(parameters('hubName'), parameters('location'), parameters('tags'), parameters('tagsByResource'), parameters('storageSku'), parameters('keyVaultSku'), parameters('enableInfrastructureEncryption'), parameters('enablePublicAccess'), parameters('virtualNetworkAddressPrefix'), parameters('enableDefaultTelemetry'))]", "useFabric": "[not(empty(parameters('fabricQueryUri')))]", - "useAzureDataExplorer": "[and(not(variables('useFabric')), not(empty(parameters('dataExplorerName'))))]", + "deployDataExplorer": "[and(not(variables('useFabric')), not(empty(parameters('dataExplorerName'))))]", "telemetryId": "00f120b5-2007-6120-0000-40b000000000", - "telemetryString": "[join(createArray(if(or(empty(parameters('remoteHubStorageUri')), empty(parameters('remoteHubStorageKey'))), '', 'R'), substring(split(parameters('storageSku'), '_')[1], 0, 1), if(not(variables('useFabric')), '', format('F{0}', parameters('fabricCapacityUnits'))), if(not(variables('useAzureDataExplorer')), '', format('X{0}', substring(parameters('dataExplorerSku'), 0, 1))), if(not(variables('useAzureDataExplorer')), '', replace(replace(replace(replace(replace(replace(replace(replace(split(split(parameters('dataExplorerSku'), 'Standard_')[1], '_')[0], 'C', ''), 'D', ''), 'E', ''), 'L', ''), 'a', ''), 'd', ''), 'i', ''), 's', '')), if(or(not(variables('useAzureDataExplorer')), equals(parameters('dataExplorerCapacity'), 1)), '', format('x{0}', parameters('dataExplorerCapacity'))), if(parameters('enablePublicAccess'), '', 'P')), '')]", + "telemetryString": "[join(createArray(if(or(empty(parameters('remoteHubStorageUri')), empty(parameters('remoteHubStorageKey'))), '', 'R'), substring(split(parameters('storageSku'), '_')[1], 0, 1), if(not(variables('useFabric')), '', format('F{0}', parameters('fabricCapacityUnits'))), if(not(variables('deployDataExplorer')), '', format('X{0}', substring(parameters('dataExplorerSku'), 0, 1))), if(not(variables('deployDataExplorer')), '', replace(replace(replace(replace(replace(replace(replace(replace(split(split(parameters('dataExplorerSku'), 'Standard_')[1], '_')[0], 'C', ''), 'D', ''), 'E', ''), 'L', ''), 'a', ''), 'd', ''), 'i', ''), 's', '')), if(or(not(variables('deployDataExplorer')), equals(parameters('dataExplorerCapacity'), 1)), '', format('x{0}', parameters('dataExplorerCapacity'))), if(parameters('enablePublicAccess'), '', 'P')), '')]", "_1.finOpsToolkitVersion": "12.0" }, "resources": { @@ -1132,33 +1170,18 @@ } } }, - "core": { + "infrastructure": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Infrastructure", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'Core')]" - }, - "scopesToMonitor": { - "value": "[parameters('scopesToMonitor')]" - }, - "msexportRetentionInDays": { - "value": "[parameters('exportRetentionInDays')]" - }, - "ingestionRetentionInMonths": { - "value": "[parameters('ingestionRetentionInMonths')]" - }, - "rawRetentionInDays": { - "value": "[parameters('dataExplorerRawRetentionInDays')]" - }, - "finalRetentionInMonths": { - "value": "[parameters('dataExplorerFinalRetentionInMonths')]" + "hub": { + "value": "[variables('hub')]" } }, "template": { @@ -1168,97 +1191,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "13614615614696914627" + "version": "0.36.177.2456", + "templateHash": "8095489755767003461" } }, "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, "_1.HubRoutingProperties": { "type": "object", "properties": { @@ -1291,9 +1228,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -1320,7 +1254,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -1328,7 +1261,7 @@ }, "description": "FinOps hub private network routing properties.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -1347,11 +1280,11 @@ "name": "Resource name.", "description": "Resource ID and name.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, - "HubAppProperties": { + "HubProperties": { "type": "object", "properties": { "id": { @@ -1360,852 +1293,797 @@ "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "location": { "type": "string" }, "tags": { "type": "object" }, - "dataFactory": { - "type": "string" + "tagsByResource": { + "type": "object" }, - "keyVault": { + "version": { "type": "string" }, - "storage": { - "type": "string" + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } } }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "scopesToMonitor": { - "type": "array", - "metadata": { - "description": "Optional. List of scope IDs to monitor and ingest cost for." - } - }, - "msexportRetentionInDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Optional. Number of days of data to retain in the msexports container. Default: 0." - } - }, - "ingestionRetentionInMonths": { - "type": "int", - "defaultValue": 13, - "metadata": { - "description": "Optional. Number of months of data to retain in the ingestion container. Default: 13." - } - }, - "rawRetentionInDays": { - "type": "int", - "defaultValue": 0, - "metadata": { - "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." + "functions": [ + { + "namespace": "__bicep", + "members": { + "getHubTags": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(parameters('hub').tags, coalesce(tryGet(parameters('hub').tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } } - }, - "finalRetentionInMonths": { - "type": "int", - "defaultValue": 13, + } + ], + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", "metadata": { - "description": "Optional. Number of months of data to retain in the Data Explorer *_final_v* tables. Default: 13." + "description": "Required. FinOps hub instance properties." } } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nWrite-Output \"Updating settings.json file...\"\nWrite-Output \" Storage account: $env:storageAccountName\"\nWrite-Output \" Container: $env:containerName\"\n\n$validateScopes = { $_.Length -gt 45 }\n\n# Initialize variables\n$fileName = 'settings.json'\n$filePath = Join-Path -Path . -ChildPath $fileName\n$newScopes = $env:scopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Download existing settings, if they exist\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\nif ($blob)\n{\n $text = Get-Content $filePath -Raw\n Write-Output \"---------\"\n Write-Output $text\n Write-Output \"---------\"\n $json = $text | ConvertFrom-Json\n Write-Output \"Existing settings.json file found. Updating...\"\n\n # Rename exportScopes to scopes + convert to object array\n if ($json.exportScopes)\n {\n Write-Output \" Updating exportScopes...\"\n if ($json.exportScopes[0] -is [string])\n {\n Write-Output \" Converting string array to object array...\"\n $json.exportScopes = @($json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } })\n if (-not ($json.exportScopes -is [array]))\n {\n Write-Output \" Converting single object to object array...\"\n $json.exportScopes = @($json.exportScopes)\n }\n }\n\n Write-Output \" Renaming to 'scopes'...\"\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\n $json.PSObject.Properties.Remove('exportScopes')\n }\n\n # Force string array to object array with unique values\n if ($json.scopes)\n {\n Write-Output \" Converting string array to object array...\"\n $scopeArray = @()\n $json.scopes | Where-Object $validateScopes | ForEach-Object { $scopeArray += $_ } | Select-Object -Unique\n $json.scopes = @() + $scopeArray\n }\n}\n\n# Set default if not found\nif (!$json)\n{\n Write-Output \"No existing settings.json file found. Creating new file...\"\n $json = [ordered]@{\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\n type = 'HubInstance'\n version = ''\n learnMore = 'https://aka.ms/finops/hubs'\n scopes = @()\n retention = @{\n 'msexports' = @{\n days = 0\n }\n 'ingestion' = @{\n months = 13\n }\n 'raw' = @{\n days = 0\n }\n 'final' = @{\n months = 13\n }\n }\n }\n\n $text = $json | ConvertTo-Json\n Write-Output \"---------\"\n Write-Output $text\n Write-Output \"---------\"\n}\n\n# Set default retention\nif (!($json.retention))\n{\n # In case the retention object is not present in the settings.json file (versions before 0.4), add it with default values\n $retention = @\"\n {\n \"msexports\": {\n \"days\": 0\n },\n \"ingestion\": {\n \"months\": 13\n },\n \"raw\": {\n \"days\": 0\n },\n \"final\": {\n \"months\": 13\n }\n }\n\"@\n $json | Add-Member -Name retention -Value (ConvertFrom-Json $retention) -MemberType NoteProperty\n}\n\n# Set or update msexports retention\nif (!($json.retention.msexports))\n{\n $json.retention | Add-Member -Name msexports -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:msexportRetentionInDays)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.msexports.days = [Int32]::Parse($env:msexportRetentionInDays)\n}\n\n# Set or update ingestion retention\nif (!($json.retention.ingestion))\n{\n $json.retention | Add-Member -Name ingestion -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:ingestionRetentionInMonths)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.ingestion.months = [Int32]::Parse($env:ingestionRetentionInMonths)\n}\n\n# Set or update raw retention\nif (!($json.retention.raw))\n{\n $json.retention | Add-Member -Name raw -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:rawRetentionInDays)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.raw.days = [Int32]::Parse($env:rawRetentionInDays)\n}\n\n# Set or update final retention\nif (!($json.retention.final))\n{\n $json.retention | Add-Member -Name final -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:finalRetentionInMonths)}\") -MemberType NoteProperty\n}\nelse\n{\n $json.retention.final.months = [Int32]::Parse($env:finalRetentionInMonths)\n}\n\n# Updating settings\nWrite-Output \"Updating version to $env:ftkVersion...\"\n$json.version = $env:ftkVersion\n$json.scopes = (@() + $json.scopes + $newScopes) | Select-Object -Unique\nif ($null -eq $json.scopes) { $json.scopes = @() }\n$text = $json | ConvertTo-Json\nWrite-Output \"---------\"\nWrite-Output $text\nWrite-Output \"---------\"\n$text | Out-File $filePath\n\n# Upload new/updated settings\nWrite-Output \"Uploading settings.json file...\"\nSet-AzStorageBlobContent @storageContext -File $filePath -Force | Out-Null\n", - "CONFIG": "config", - "INGESTION": "ingestion", - "finOpsToolkitVersion": "12.0" + "nsgName": "[format('{0}-nsg', parameters('hub').routing.networkName)]", + "finopsHubSubnetName": "private-endpoint-subnet", + "scriptSubnetName": "script-subnet", + "dataExplorerSubnetName": "dataExplorer-subnet", + "subnets": "[if(not(parameters('hub').options.privateRouting), createArray(), createArray(createObject('name', variables('finopsHubSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 0), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('scriptSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'delegations', createArray(createObject('name', 'Microsoft.ContainerInstance/containerGroups', 'properties', createObject('serviceName', 'Microsoft.ContainerInstance/containerGroups'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('dataExplorerSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 27, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName')))))))]" }, "resources": { - "dataFactory::dataset_config": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]", + "vNet::finopsHubSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('finopsHubSubnetName'))]", + "dependsOn": [ + "vNet" + ] + }, + "vNet::scriptSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('scriptSubnetName'))]", + "dependsOn": [ + "vNet" + ] + }, + "vNet::dataExplorerSubnet": { + "condition": "[parameters('hub').options.privateRouting]", + "existing": true, + "type": "Microsoft.Network/virtualNetworks/subnets", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('dataExplorerSubnetName'))]", + "dependsOn": [ + "vNet" + ] + }, + "blobPrivateDnsZone::blobPrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.blob.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.blob.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "linkedServiceName": { - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - }, - "type": "Json", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().fileName}", - "type": "Expression" - }, - "folderPath": { - "value": "@{dataset().folderPath}", - "type": "Expression" - } - } - }, - "parameters": { - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[reference('configContainer').outputs.containerName.value]" - } + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" } }, "dependsOn": [ - "appRegistration", - "configContainer" + "blobPrivateDnsZone" ] }, - "dataFactory::dataset_ingestion": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('INGESTION'))]", + "dfsPrivateDnsZone::dfsPrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.dfs.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.dfs.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" - }, - "fileSystem": "[reference('ingestionContainer').outputs.containerName.value]" - } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" } }, "dependsOn": [ - "appRegistration", - "ingestionContainer" + "dfsPrivateDnsZone" ] }, - "dataFactory::dataset_ingestion_files": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', variables('INGESTION')))]", + "queuePrivateDnsZone::queuePrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.queue.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.queue.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { - "annotations": [], - "parameters": { - "folderPath": { - "type": "String" + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + } + }, + "dependsOn": [ + "queuePrivateDnsZone" + ] + }, + "tablePrivateDnsZone::tablePrivateDnsZoneLink": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.table.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.table.name), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + } + }, + "dependsOn": [ + "tablePrivateDnsZone" + ] + }, + "scriptEndpoint::scriptPrivateDnsZoneGroup": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', string(parameters('hub').routing.scriptStorage)), 'blob-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', string(parameters('hub').routing.dnsZones.blob.name))]" + } } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileSystem": "[reference('ingestionContainer').outputs.containerName.value]", - "folderPath": { - "value": "@dataset().folderPath", - "type": "Expression" + ] + }, + "dependsOn": [ + "blobPrivateDnsZone", + "scriptEndpoint" + ] + }, + "nsg": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2023-11-01", + "name": "[variables('nsgName')]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/networkSecurityGroups')]", + "properties": { + "securityRules": [ + { + "name": "AllowVnetInBound", + "properties": { + "priority": 100, + "direction": "Inbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "VirtualNetwork", + "destinationAddressPrefix": "VirtualNetwork" + } + }, + { + "name": "AllowAzureLoadBalancerInBound", + "properties": { + "priority": 200, + "direction": "Inbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "AzureLoadBalancer", + "destinationAddressPrefix": "*" + } + }, + { + "name": "DenyAllInBound", + "properties": { + "priority": 4096, + "direction": "Inbound", + "access": "Deny", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "*" + } + }, + { + "name": "AllowVnetOutBound", + "properties": { + "priority": 100, + "direction": "Outbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "VirtualNetwork", + "destinationAddressPrefix": "VirtualNetwork" + } + }, + { + "name": "AllowInternetOutBound", + "properties": { + "priority": 200, + "direction": "Outbound", + "access": "Allow", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "Internet" + } + }, + { + "name": "DenyAllOutBound", + "properties": { + "priority": 4096, + "direction": "Outbound", + "access": "Deny", + "protocol": "*", + "sourcePortRange": "*", + "destinationPortRange": "*", + "sourceAddressPrefix": "*", + "destinationAddressPrefix": "*" } } + ] + } + }, + "vNet": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2023-11-01", + "name": "[parameters('hub').routing.networkName]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/virtualNetworks')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[parameters('hub').options.networkAddressPrefix]" + ] }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - } + "subnets": "[variables('subnets')]" }, "dependsOn": [ - "appRegistration", - "ingestionContainer" + "nsg" ] }, - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", + "blobPrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, "dependsOn": [ - "appRegistration" + "vNet" ] }, - "infrastructure": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core_Infrastructure", + "dfsPrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.dfs.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] + }, + "queuePrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.queue.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] + }, + "tablePrivateDnsZone": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[string(parameters('hub').routing.dnsZones.table.name)]", + "location": "global", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", + "properties": {}, + "dependsOn": [ + "vNet" + ] + }, + "scriptStorageAccount": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[string(parameters('hub').routing.scriptStorage)]", + "location": "[parameters('hub').location]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/storageAccounts')]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "supportsHttpsTrafficOnly": true, + "allowSharedKeyAccess": true, + "isHnsEnabled": false, + "minimumTlsVersion": "TLS1_2", + "allowBlobPublicAccess": false, + "publicNetworkAccess": "Enabled", + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "Deny", + "virtualNetworkRules": [ + { + "id": "[parameters('hub').routing.subnets.scripts]", + "action": "Allow" + } + ] + } + }, + "dependsOn": [ + "vNet::scriptSubnet" + ] + }, + "scriptEndpoint": { + "condition": "[parameters('hub').options.privateRouting]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', string(parameters('hub').routing.scriptStorage))]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('hub').routing.subnets.storage]" }, - "mode": "Incremental", - "parameters": { - "hub": { - "value": "[parameters('app').hub]" + "privateLinkServiceConnections": [ + { + "name": "scriptLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', string(parameters('hub').routing.scriptStorage))]", + "groupIds": [ + "blob" + ] + } } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5983517136492624194" + ] + }, + "dependsOn": [ + "scriptStorageAccount", + "vNet::scriptSubnet" + ] + } + }, + "outputs": { + "config": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "FinOps hub configuration settings." + }, + "value": "[parameters('hub')]" + }, + "vNetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the virtual network." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks', parameters('hub').routing.networkName))]" + }, + "vNetAddressSpace": { + "type": "array", + "metadata": { + "description": "Virtual network address prefixes." + }, + "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').addressSpace.addressPrefixes)]" + }, + "vNetSubnets": { + "type": "array", + "metadata": { + "description": "Virtual network subnets." + }, + "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').subnets)]" + }, + "finopsHubSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the FinOps hub network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('finopsHubSubnetName')))]" + }, + "scriptSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the script storage account network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('scriptSubnetName')))]" + }, + "dataExplorerSubnetId": { + "type": "string", + "metadata": { + "description": "Resource ID of the Data Explorer network subnet." + }, + "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('dataExplorerSubnetName')))]" + } + } + } + } + }, + "core": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "hub": { + "value": "[variables('hub')]" + }, + "telemetryString": { + "value": "[variables('telemetryString')]" + }, + "scopesToMonitor": { + "value": "[parameters('scopesToMonitor')]" + }, + "msexportRetentionInDays": { + "value": "[parameters('exportRetentionInDays')]" + }, + "ingestionRetentionInMonths": { + "value": "[parameters('ingestionRetentionInMonths')]" + }, + "rawRetentionInDays": { + "value": "[parameters('dataExplorerRawRetentionInDays')]" + }, + "finalRetentionInMonths": { + "value": "[parameters('dataExplorerFinalRetentionInMonths')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "15396896523851766061" + } + }, + "definitions": { + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } - }, - "definitions": { - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } + "keyVault": { + "type": "string" }, - "HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getHubTags": { - "parameters": [ - { - "$ref": "#/definitions/HubProperties", - "name": "hub" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(parameters('hub').tags, coalesce(tryGet(parameters('hub').tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub instance.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "hub": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "Required. FinOps hub instance properties." - } - } - }, - "variables": { - "nsgName": "[format('{0}-nsg', parameters('hub').routing.networkName)]", - "finopsHubSubnetName": "private-endpoint-subnet", - "scriptSubnetName": "script-subnet", - "dataExplorerSubnetName": "dataExplorer-subnet", - "subnets": "[if(not(parameters('hub').options.privateRouting), createArray(), createArray(createObject('name', variables('finopsHubSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 0), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('scriptSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 28, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName'))), 'delegations', createArray(createObject('name', 'Microsoft.ContainerInstance/containerGroups', 'properties', createObject('serviceName', 'Microsoft.ContainerInstance/containerGroups'))), 'serviceEndpoints', createArray(createObject('service', 'Microsoft.Storage')))), createObject('name', variables('dataExplorerSubnetName'), 'properties', createObject('addressPrefix', cidrSubnet(parameters('hub').options.networkAddressPrefix, 27, 1), 'networkSecurityGroup', createObject('id', resourceId('Microsoft.Network/networkSecurityGroups', variables('nsgName')))))))]" - }, - "resources": { - "vNet::finopsHubSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('finopsHubSubnetName'))]", - "dependsOn": [ - "vNet" - ] - }, - "vNet::scriptSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('scriptSubnetName'))]", - "dependsOn": [ - "vNet" - ] - }, - "vNet::dataExplorerSubnet": { - "condition": "[parameters('hub').options.privateRouting]", - "existing": true, - "type": "Microsoft.Network/virtualNetworks/subnets", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', parameters('hub').routing.networkName, variables('dataExplorerSubnetName'))]", - "dependsOn": [ - "vNet" - ] + "scripts": { + "type": "string" }, - "blobPrivateDnsZone::blobPrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.blob.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.blob.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "blobPrivateDnsZone" - ] + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "dfsPrivateDnsZone::dfsPrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.dfs.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.dfs.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "dfsPrivateDnsZone" - ] + "keyVaultSku": { + "type": "string" }, - "queuePrivateDnsZone::queuePrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.queue.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.queue.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "queuePrivateDnsZone" - ] + "networkAddressPrefix": { + "type": "string" }, - "tablePrivateDnsZone::tablePrivateDnsZoneLink": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', string(parameters('hub').routing.dnsZones.table.name), format('{0}-link', replace(string(parameters('hub').routing.dnsZones.table.name), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[parameters('hub').routing.networkId]" - } - }, - "dependsOn": [ - "tablePrivateDnsZone" - ] + "privateRouting": { + "type": "bool" }, - "scriptEndpoint::scriptPrivateDnsZoneGroup": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('hub').routing.scriptStorage), 'blob-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', string(parameters('hub').routing.dnsZones.blob.name))]" - } - } - ] - }, - "dependsOn": [ - "blobPrivateDnsZone", - "scriptEndpoint" - ] + "publisherIsolation": { + "type": "bool" }, - "nsg": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/networkSecurityGroups", - "apiVersion": "2023-11-01", - "name": "[variables('nsgName')]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/networkSecurityGroups')]", - "properties": { - "securityRules": [ - { - "name": "AllowVnetInBound", - "properties": { - "priority": 100, - "direction": "Inbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "VirtualNetwork", - "destinationAddressPrefix": "VirtualNetwork" - } - }, - { - "name": "AllowAzureLoadBalancerInBound", - "properties": { - "priority": 200, - "direction": "Inbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "AzureLoadBalancer", - "destinationAddressPrefix": "*" - } - }, - { - "name": "DenyAllInBound", - "properties": { - "priority": 4096, - "direction": "Inbound", - "access": "Deny", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*" - } - }, - { - "name": "AllowVnetOutBound", - "properties": { - "priority": 100, - "direction": "Outbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "VirtualNetwork", - "destinationAddressPrefix": "VirtualNetwork" - } - }, - { - "name": "AllowInternetOutBound", - "properties": { - "priority": 200, - "direction": "Outbound", - "access": "Allow", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "Internet" - } - }, - { - "name": "DenyAllOutBound", - "properties": { - "priority": 4096, - "direction": "Outbound", - "access": "Deny", - "protocol": "*", - "sourcePortRange": "*", - "destinationPortRange": "*", - "sourceAddressPrefix": "*", - "destinationAddressPrefix": "*" - } - } - ] - } + "storageInfrastructureEncryption": { + "type": "bool" }, - "vNet": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2023-11-01", - "name": "[parameters('hub').routing.networkName]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/virtualNetworks')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[parameters('hub').options.networkAddressPrefix]" - ] - }, - "subnets": "[variables('subnets')]" - }, - "dependsOn": [ - "nsg" - ] - }, - "blobPrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.blob.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.dfs.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "queuePrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.queue.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "tablePrivateDnsZone": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[string(parameters('hub').routing.dnsZones.table.name)]", - "location": "global", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/privateDnsZones')]", - "properties": {}, - "dependsOn": [ - "vNet" - ] - }, - "scriptStorageAccount": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('hub').routing.scriptStorage]", - "location": "[parameters('hub').location]", - "sku": { - "name": "Standard_LRS" - }, - "kind": "StorageV2", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Storage/storageAccounts')]", - "properties": { - "supportsHttpsTrafficOnly": true, - "allowSharedKeyAccess": true, - "isHnsEnabled": false, - "minimumTlsVersion": "TLS1_2", - "allowBlobPublicAccess": false, - "publicNetworkAccess": "Enabled", - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "Deny", - "virtualNetworkRules": [ - { - "id": "[parameters('hub').routing.subnets.scripts]", - "action": "Allow" - } - ] - } - }, - "dependsOn": [ - "vNet::scriptSubnet" - ] - }, - "scriptEndpoint": { - "condition": "[parameters('hub').options.privateRouting]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('hub').routing.scriptStorage)]", - "location": "[parameters('hub').location]", - "tags": "[__bicep.getHubTags(parameters('hub'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('hub').routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "scriptLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('hub').routing.scriptStorage)]", - "groupIds": [ - "blob" - ] - } - } - ] - }, - "dependsOn": [ - "scriptStorageAccount", - "vNet::scriptSubnet" - ] + "storageSku": { + "type": "string" } - }, - "outputs": { - "config": { - "$ref": "#/definitions/HubProperties", - "metadata": { - "description": "FinOps hub configuration settings." - }, - "value": "[parameters('hub')]" - }, - "vNetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the virtual network." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks', parameters('hub').routing.networkName))]" - }, - "vNetAddressSpace": { - "type": "array", - "metadata": { - "description": "Virtual network address prefixes." - }, - "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').addressSpace.addressPrefixes)]" - }, - "vNetSubnets": { - "type": "array", - "metadata": { - "description": "Virtual network subnets." - }, - "value": "[if(not(parameters('hub').options.privateRouting), createArray(), reference('vNet').subnets)]" - }, - "finopsHubSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the FinOps hub network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('finopsHubSubnetName')))]" - }, - "scriptSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the script storage account network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('scriptSubnetName')))]" - }, - "dataExplorerSubnetId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Explorer network subnet." - }, - "value": "[if(not(parameters('hub').options.privateRouting), '', resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('hub').routing.networkName, variables('dataExplorerSubnetName')))]" + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "Required. FinOps hub instance to deploy the app to." + } + }, + "scopesToMonitor": { + "type": "array", + "metadata": { + "description": "Optional. List of scope IDs to monitor and ingest cost for." + } + }, + "msexportRetentionInDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Number of days of data to retain in the msexports container. Default: 0." + } + }, + "ingestionRetentionInMonths": { + "type": "int", + "defaultValue": 13, + "metadata": { + "description": "Optional. Number of months of data to retain in the ingestion container. Default: 13." + } + }, + "rawRetentionInDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." + } + }, + "finalRetentionInMonths": { + "type": "int", + "defaultValue": 13, + "metadata": { + "description": "Optional. Number of months of data to retain in the Data Explorer *_final_v* tables. Default: 13." } }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + } + }, + "variables": { + "$fxv#0": "12.0", + "$fxv#1": "12.0", + "$fxv#2": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nWrite-Output \"Updating settings.json file...\"\r\nWrite-Output \" Storage account: $env:storageAccountName\"\r\nWrite-Output \" Container: $env:containerName\"\r\n\r\n$validateScopes = { $_.Length -gt 45 }\r\n\r\n# Initialize variables\r\n$fileName = 'settings.json'\r\n$filePath = Join-Path -Path . -ChildPath $fileName\r\n$newScopes = $env:scopes.Split('|') | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } }\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Download existing settings, if they exist\r\n$blob = Get-AzStorageBlobContent @storageContext -Blob $fileName -Destination $filePath -Force\r\nif ($blob)\r\n{\r\n $text = Get-Content $filePath -Raw\r\n Write-Output \"---------\"\r\n Write-Output $text\r\n Write-Output \"---------\"\r\n $json = $text | ConvertFrom-Json\r\n Write-Output \"Existing settings.json file found. Updating...\"\r\n\r\n # Rename exportScopes to scopes + convert to object array\r\n if ($json.exportScopes)\r\n {\r\n Write-Output \" Updating exportScopes...\"\r\n if ($json.exportScopes[0] -is [string])\r\n {\r\n Write-Output \" Converting string array to object array...\"\r\n $json.exportScopes = @($json.exportScopes | Where-Object $validateScopes | ForEach-Object { @{ scope = $_ } })\r\n if (-not ($json.exportScopes -is [array]))\r\n {\r\n Write-Output \" Converting single object to object array...\"\r\n $json.exportScopes = @($json.exportScopes)\r\n }\r\n }\r\n\r\n Write-Output \" Renaming to 'scopes'...\"\r\n $json | Add-Member -MemberType NoteProperty -Name scopes -Value $json.exportScopes\r\n $json.PSObject.Properties.Remove('exportScopes')\r\n }\r\n\r\n # Force string array to object array with unique values\r\n if ($json.scopes)\r\n {\r\n Write-Output \" Converting string array to object array...\"\r\n $scopeArray = @()\r\n $json.scopes | Where-Object $validateScopes | ForEach-Object { $scopeArray += $_ } | Select-Object -Unique\r\n $json.scopes = @() + $scopeArray\r\n }\r\n}\r\n\r\n# Set default if not found\r\nif (!$json)\r\n{\r\n Write-Output \"No existing settings.json file found. Creating new file...\"\r\n $json = [ordered]@{\r\n '$schema' = 'https://aka.ms/finops/hubs/settings-schema'\r\n type = 'HubInstance'\r\n version = ''\r\n learnMore = 'https://aka.ms/finops/hubs'\r\n scopes = @()\r\n retention = @{\r\n 'msexports' = @{\r\n days = 0\r\n }\r\n 'ingestion' = @{\r\n months = 13\r\n }\r\n 'raw' = @{\r\n days = 0\r\n }\r\n 'final' = @{\r\n months = 13\r\n }\r\n }\r\n }\r\n\r\n $text = $json | ConvertTo-Json\r\n Write-Output \"---------\"\r\n Write-Output $text\r\n Write-Output \"---------\"\r\n}\r\n\r\n# Set default retention\r\nif (!($json.retention))\r\n{\r\n # In case the retention object is not present in the settings.json file (versions before 0.4), add it with default values\r\n $retention = @\"\r\n {\r\n \"msexports\": {\r\n \"days\": 0\r\n },\r\n \"ingestion\": {\r\n \"months\": 13\r\n },\r\n \"raw\": {\r\n \"days\": 0\r\n },\r\n \"final\": {\r\n \"months\": 13\r\n }\r\n }\r\n\"@\r\n $json | Add-Member -Name retention -Value (ConvertFrom-Json $retention) -MemberType NoteProperty\r\n}\r\n\r\n# Set or update msexports retention\r\nif (!($json.retention.msexports))\r\n{\r\n $json.retention | Add-Member -Name msexports -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:msexportRetentionInDays)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.msexports.days = [Int32]::Parse($env:msexportRetentionInDays)\r\n}\r\n\r\n# Set or update ingestion retention\r\nif (!($json.retention.ingestion))\r\n{\r\n $json.retention | Add-Member -Name ingestion -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:ingestionRetentionInMonths)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.ingestion.months = [Int32]::Parse($env:ingestionRetentionInMonths)\r\n}\r\n\r\n# Set or update raw retention\r\nif (!($json.retention.raw))\r\n{\r\n $json.retention | Add-Member -Name raw -Value (ConvertFrom-Json \"{\"\"days\"\":$($env:rawRetentionInDays)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.raw.days = [Int32]::Parse($env:rawRetentionInDays)\r\n}\r\n\r\n# Set or update final retention\r\nif (!($json.retention.final))\r\n{\r\n $json.retention | Add-Member -Name final -Value (ConvertFrom-Json \"{\"\"months\"\":$($env:finalRetentionInMonths)}\") -MemberType NoteProperty\r\n}\r\nelse\r\n{\r\n $json.retention.final.months = [Int32]::Parse($env:finalRetentionInMonths)\r\n}\r\n\r\n# Updating settings\r\nWrite-Output \"Updating version to $env:ftkVersion...\"\r\n$json.version = $env:ftkVersion\r\n$json.scopes = (@() + $json.scopes + $newScopes) | Select-Object -Unique\r\nif ($null -eq $json.scopes) { $json.scopes = @() }\r\n$text = $json | ConvertTo-Json\r\nWrite-Output \"---------\"\r\nWrite-Output $text\r\nWrite-Output \"---------\"\r\n$text | Out-File $filePath\r\n\r\n# Upload new/updated settings\r\nWrite-Output \"Uploading settings.json file...\"\r\nSet-AzStorageBlobContent @storageContext -File $filePath -Force | Out-Null\r\n" + }, + "resources": { "appRegistration": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "Microsoft.FinOpsHubs.Core_Register", "properties": { "expressionEvaluationOptions": { @@ -2213,17 +2091,32 @@ }, "mode": "Incremental", "parameters": { - "app": { - "value": "[parameters('app')]" + "hub": { + "value": "[parameters('hub')]" }, - "version": { - "value": "[variables('finOpsToolkitVersion')]" + "publisher": { + "value": "Microsoft FinOps hubs" + }, + "namespace": { + "value": "Microsoft.FinOpsHubs" + }, + "appName": { + "value": "Core" + }, + "displayName": { + "value": "FinOps hub core" + }, + "appVersion": { + "value": "[variables('$fxv#0')]" }, "features": { "value": [ "DataFactory", "Storage" ] + }, + "telemetryString": { + "value": "[parameters('telemetryString')]" } }, "template": { @@ -2233,142 +2126,53 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5436870138046688593" + "version": "0.36.177.2456", + "templateHash": "15179190433979236138" } }, "definitions": { - "_1.HubProperties": { + "_1.HubRoutingProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "name": { + "networkId": { "type": "string" }, - "location": { + "networkName": { "type": "string" }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { + "scriptStorage": { "type": "string" }, - "options": { + "dnsZones": { "type": "object", "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "publisherIsolation": { - "type": "bool" + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" }, - "storageInfrastructureEncryption": { - "type": "bool" + "queue": { + "$ref": "#/definitions/_1.IdNameObject" }, - "storageSku": { - "type": "string" + "table": { + "$ref": "#/definitions/_1.IdNameObject" } } }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { + "subnets": { "type": "object", "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { "type": "string" } } @@ -2385,7 +2189,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -2433,21 +2236,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/HubProperties" + }, "dataFactory": { "type": "string" }, @@ -2456,33 +2273,147 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" } } + }, + "HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } }, "functions": [ { "namespace": "__bicep", "members": { - "getAppPublisherTags": { + "getAppTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + }, + { + "type": "bool", + "nullable": true, + "name": "forceAppTags" + } + ], + "output": { + "type": "object", + "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "getPublisherTags": { "parameters": [ { "$ref": "#/definitions/HubAppProperties", @@ -2495,7 +2426,7 @@ ], "output": { "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" }, "metadata": { "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", @@ -2503,41 +2434,175 @@ "sourceTemplate": "hub-types.bicep" } } + }, + "newApp": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" + }, + { + "type": "string", + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "appPartialName" + }, + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" + } + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" + }, + "metadata": { + "description": "Creates a new FinOps hub app configuration object.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "version": { - "type": "string", - "metadata": { - "description": "Required. Version number of the FinOps hub app." - } }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." - } + { + "namespace": "_1", + "members": { + "newAppInternal": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherSuffix" + }, + { + "type": "object", + "name": "publisherTags" + }, + { + "type": "string", + "name": "appName" + }, + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" + } + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": { + "name": "[parameters('appName')]", + "displayName": "[parameters('appDisplayName')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", + "publisher": { + "name": "[parameters('publisherName')]", + "displayName": "[parameters('publisherDisplayName')]", + "suffix": "[parameters('publisherSuffix')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" + }, + "hub": "[parameters('hub')]", + "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "safeStorageName": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "string", + "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "Required. FinOps hub instance properties." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app publisher." + } + }, + "namespace": { + "type": "string", + "metadata": { + "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "appName": { + "type": "string", + "metadata": { + "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app." + } + }, + "appVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Version number of the FinOps hub app." + } }, - "storageRoles": { + "features": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/HubAppFeature" }, "defaultValue": [], "metadata": { - "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." } }, "telemetryString": { @@ -2549,11 +2614,11 @@ } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", + "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", "telemetryProps": { "mode": "Incremental", "template": { @@ -2561,158 +2626,30 @@ "contentVersion": "1.0.0.0", "metadata": { "_generator": { - "name": "[format('FTK: {0}', parameters('app').id)]", - "version": "[parameters('version')]" + "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", + "version": "[parameters('appVersion')]" } }, "resources": [] } }, - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", - "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" }, "resources": { - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", - "properties": { - "name": "[parameters('app').storage]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "storageAccount" - ] - }, - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", - "properties": { - "name": "[parameters('app').keyVault]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "keyVault" - ] - }, - "dataFactory::managedVirtualNetwork": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "properties": {}, - "dependsOn": [ - "dataFactory" - ] - }, - "dataFactory::managedIntegrationRuntime": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", - "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "default", - "type": "ManagedVirtualNetworkReference" - }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('app').hub.location]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 - } - } - } - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedVirtualNetwork" - ] - }, - "dataFactory::linkedService_keyVault": { - "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "keyVault" - ] - }, - "dataFactory::linkedService_storageAccount": { - "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "storageAccount" - ] - }, "storageAccount::blobService": { "condition": "[variables('usesStorage')]", "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", + "name": "[format('{0}/{1}', variables('app').storage, 'default')]", "dependsOn": [ "storageAccount" ] }, "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", + "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", "properties": { "privateDnsZoneConfigs": [ { @@ -2728,10 +2665,10 @@ ] }, "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", "properties": { "privateDnsZoneConfigs": [ { @@ -2750,7 +2687,7 @@ "condition": "[variables('usesKeyVault')]", "type": "Microsoft.KeyVault/vaults/accessPolicies", "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", + "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", "properties": { "accessPolicies": [ { @@ -2770,15 +2707,15 @@ ] }, "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", "apiVersion": "2024-06-01", "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", "properties": { "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" + "id": "[parameters('hub').routing.networkId]" }, "registrationEnabled": false }, @@ -2787,10 +2724,10 @@ ] }, "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", + "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", "properties": { "privateDnsZoneConfigs": [ { @@ -2807,20 +2744,20 @@ ] }, "appTelemetry": { - "condition": "[parameters('app').hub.options.enableTelemetry]", + "condition": "[parameters('hub').options.enableTelemetry]", "type": "Microsoft.Resources/deployments", "apiVersion": "2022-09-01", "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", + "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", "properties": "[variables('telemetryProps')]" }, "dataFactory": { "condition": "[variables('usesDataFactory')]", "type": "Microsoft.DataFactory/factories", "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", + "name": "[variables('app').dataFactory]", + "location": "[variables('app').hub.location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", "identity": { "type": "SystemAssigned" }, @@ -2830,92 +2767,42 @@ } } }, - "storageRoleAssignments": { - "copy": { - "name": "storageRoleAssignments", - "count": "[length(variables('factoryStorageRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "storageAccount" - ] - }, - "triggerManagerIdentity": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "dependsOn": [ - "dataFactory" - ] - }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "triggerManagerIdentity" - ] - }, "storageAccount": { "condition": "[variables('usesStorage')]", "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "location": "[parameters('app').hub.location]", + "name": "[variables('app').storage]", + "location": "[parameters('hub').location]", "sku": { - "name": "[parameters('app').hub.options.storageSku]" + "name": "[parameters('hub').options.storageSku]" }, "kind": "BlockBlobStorage", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" }, "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "existing": true, "type": "Microsoft.Network/privateDnsZones", "apiVersion": "2024-06-01", "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" }, "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "name": "[format('{0}-blob-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" + "id": "[parameters('hub').routing.subnets.storage]" }, "privateLinkServiceConnections": [ { "name": "blobLink", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", "groupIds": [ "blob" ] @@ -2928,28 +2815,28 @@ ] }, "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "existing": true, "type": "Microsoft.Network/privateDnsZones", "apiVersion": "2024-06-01", "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" }, "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "name": "[format('{0}-dfs-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" + "id": "[parameters('hub').routing.subnets.storage]" }, "privateLinkServiceConnections": [ { "name": "dfsLink", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", "groupIds": [ "dfs" ] @@ -2965,12 +2852,12 @@ "condition": "[variables('usesKeyVault')]", "type": "Microsoft.KeyVault/vaults", "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "name": "[variables('app').keyVault]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", "properties": { "sku": { - "name": "[parameters('app').hub.options.keyVaultSku]", + "name": "[parameters('hub').options.keyVaultSku]", "family": "A" }, "enabledForDeployment": true, @@ -2994,7 +2881,7 @@ ], "networkAcls": { "bypass": "AzureServices", - "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" } }, "dependsOn": [ @@ -3002,30 +2889,30 @@ ] }, "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateDnsZones", "apiVersion": "2024-06-01", "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", "properties": {} }, "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", "type": "Microsoft.Network/privateEndpoints", "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('app').keyVault)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "name": "[format('{0}-ep', variables('app').keyVault)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { "subnet": { - "id": "[parameters('app').hub.routing.subnets.keyVault]" + "id": "[parameters('hub').routing.subnets.keyVault]" }, "privateLinkServiceConnections": [ { "name": "keyVaultLink", "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", "groupIds": [ "vault" ] @@ -3036,305 +2923,356 @@ "dependsOn": [ "keyVault" ] + } + }, + "outputs": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "FinOps hub app configuration." + }, + "value": "[variables('app')]" }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetKeyVaultPrivateEndpointConnections", + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + } + } + } + } + }, + "configContainer": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_Storage.ConfigContainer", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('appRegistration').outputs.app.value]" + }, + "container": { + "value": "config" + }, + "forceCreateBlobManagerIdentity": { + "value": true + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "13960345490822271084" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - } + "name": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } } }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", - "getStoragePrivateEndpointConnections", - "keyVault" - ] + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", + "_1.HubRoutingProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "networkId": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } + "networkName": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" } } } }, - "dependsOn": [ - "getKeyVaultPrivateEndpointConnections", - "keyVault" - ] + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "getStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetStoragePrivateEndpointConnections", + "_1.IdNameObject": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - } + "id": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } + "name": { + "type": "string" } }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", - "stopTriggers", - "storageAccount" - ] + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "approveStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveStoragePrivateEndpointConnections", + "HubAppProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "name": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } + "displayName": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" } } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" } }, - "dependsOn": [ - "getStoragePrivateEndpointConnections", - "storageAccount" - ] + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app that storage is getting updated for." + } + }, + "container": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage container to create or update." + } + }, + "files": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." + } + }, + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" + }, + "resources": { + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "properties": { + "publicAccess": "None", + "metadata": {} + } + }, + "storageAccount::blobService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]" }, - "stopTriggers": { + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Identity', deployment().name)]", "properties": { "expressionEvaluationOptions": { "scope": "inner" @@ -3345,28 +3283,15 @@ "value": "[parameters('app')]" }, "identityName": { - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" + "value": "[format('{0}_blobManager', parameters('app').storage)]" }, - "arguments": { - "value": "-Stop" + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, - "environmentVariables": { + "roles": { "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('app').dataFactory]" - } + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" ] } }, @@ -3377,22 +3302,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "version": "0.36.177.2456", + "templateHash": "4534337491931150093" } }, "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, "_1.HubProperties": { "type": "object", "properties": { @@ -3511,9 +3425,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -3540,7 +3451,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -3574,21 +3484,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -3597,21 +3521,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -3619,500 +3544,151 @@ } } }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." + "description": "Required. FinOps hub app the identity is associated with." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Required. Name of the user assigned identity." } }, - "arguments": { + "roleAssignmentResourceId": { "type": "string", - "defaultValue": "", "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." + "description": "Required. Resource ID of the resource access is being granted for." } }, - "environmentVariables": { + "roles": { "type": "array", "items": { - "$ref": "#/definitions/EnvironmentVariable" + "type": "string" }, - "defaultValue": [], "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "description": "Required. List of RBAC role assignment GUIDs." } } }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, "resources": { "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", "location": "[parameters('app').hub.location]" }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" + "count": "[length(parameters('roles'))]" }, - "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] } - } - } - }, - "dependsOn": [ - "appTelemetry", - "dataFactory", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" - ] - } - }, - "outputs": { - "dataFactoryId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Factory instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" - }, - "keyVaultId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Key Vault instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" - }, - "storageAccountId": { - "type": "string", - "metadata": { - "description": "Resource ID of the storage account instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - }, - "triggerManagerIdentityName": { - "type": "string", - "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." - }, - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - } - } - } - }, - "dependsOn": [ - "infrastructure" - ] - }, - "configContainer": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core_Storage.ConfigContainer", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "container": { - "value": "[variables('CONFIG')]" - }, - "forceCreateBlobManagerIdentity": { - "value": true - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "7314877606184110283" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" }, - "storageInfrastructureEncryption": { - "type": "bool" + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." + }, + "value": "[parameters('identityName')]" }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" } } } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } } }, - "_1.HubRoutingProperties": { - "type": "object", + "uploadFiles": { + "condition": "[variables('hasFiles')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Upload', deployment().name)]", "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." - } - }, - "container": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage container to create or update." - } - }, - "files": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." - } - }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" - }, - "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", - "properties": { - "publicAccess": "None", - "metadata": {} - } - }, - "storageAccount::blobService": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" - }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Identity', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "roles": { - "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" - ] + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[reference('identity').outputs.name.value]" + }, + "environmentVariables": { + "value": [ + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" + }, + { + "name": "files", + "value": "[string(parameters('files'))]" + } + ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" } }, "template": { @@ -4122,11 +3698,22 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2980528181281411934" + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } }, "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, "_1.HubProperties": { "type": "object", "properties": { @@ -4245,9 +3832,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -4274,7 +3858,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -4308,21 +3891,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -4331,21 +3928,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -4353,779 +3951,392 @@ } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the identity is associated with." + "description": "Required. FinOps hub app the deployment script is being run for." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the user assigned identity." + "description": "Required. Name of the managed identity to create." } }, - "roleAssignmentResourceId": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." } }, - "roles": { - "type": "array", - "items": { - "type": "string" - }, + "scriptContent": { + "type": "string", "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." + "description": "Required. Name of the deployment script to create." } - } - }, - "resources": { - "identity": { + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", "location": "[parameters('app').hub.location]" }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" + "count": "[length(variables('privateEndpointDeploymentRoles'))]" }, + "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } }, - "value": "[reference('identity').principalId]" + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } - } + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Upload', deployment().name)]", + "filesUploaded": { + "type": "int", + "metadata": { + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" + }, + "identityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + } + } + } + }, + "dependsOn": [ + "appRegistration" + ] + }, + "ingestionContainer": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_Storage.IngestionContainer", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('appRegistration').outputs.app.value]" + }, + "container": { + "value": "ingestion" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "13960345490822271084" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "queue": { + "$ref": "#/definitions/_1.IdNameObject" }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } + "table": { + "$ref": "#/definitions/_1.IdNameObject" } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "keyVault": { + "type": "string" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] + "scripts": { + "type": "string" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "storage": { + "type": "string" } } } }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "containerName": { - "type": "string", - "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" - }, - "filesUploaded": { - "type": "int", - "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" - }, - "identityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" - }, - "identityName": { - "type": "string", "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "identityPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" - } - } - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "ingestionContainer": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core_Storage.IngestionContainer", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "container": { - "value": "[variables('INGESTION')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "7314877606184110283" - } - }, - "definitions": { - "_1.HubProperties": { + "_1.IdNameObject": { "type": "object", "properties": { "id": { "type": "string" }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { "name": { "type": "string" }, - "location": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { + "publisher": { "type": "object", "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { + "name": { "type": "string" }, - "networkAddressPrefix": { + "displayName": { "type": "string" }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { + "suffix": { "type": "string" + }, + "tags": { + "type": "object" } } }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" + "hub": { + "$ref": "#/definitions/_1.HubProperties" }, "dataFactory": { "type": "string" @@ -5135,21 +4346,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -5186,7 +4398,7 @@ } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", + "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", "fileCount": "[length(items(parameters('files')))]", "hasFiles": "[greater(variables('fileCount'), 0)]" }, @@ -5215,7 +4427,7 @@ "identity": { "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}.Identity', deployment().name)]", "properties": { "expressionEvaluationOptions": { @@ -5246,8 +4458,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2980528181281411934" + "version": "0.36.177.2456", + "templateHash": "4534337491931150093" } }, "definitions": { @@ -5369,9 +4581,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -5398,7 +4607,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -5432,21 +4640,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -5455,21 +4677,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -5481,7 +4704,7 @@ { "namespace": "__bicep", "members": { - "getAppPublisherTags": { + "getPublisherTags": { "parameters": [ { "$ref": "#/definitions/HubAppProperties", @@ -5494,7 +4717,7 @@ ], "output": { "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" }, "metadata": { "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", @@ -5540,7 +4763,7 @@ "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", "location": "[parameters('app').hub.location]" }, "identityRoleAssignments": { @@ -5590,7 +4813,7 @@ "uploadFiles": { "condition": "[variables('hasFiles')]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "[format('{0}.Upload', deployment().name)]", "properties": { "expressionEvaluationOptions": { @@ -5631,8 +4854,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } }, "definitions": { @@ -5765,9 +4988,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -5794,7 +5014,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -5828,21 +5047,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -5851,21 +5084,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -5919,8 +5153,7 @@ }, "variables": { "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" }, "resources": { "identity": { @@ -5969,7 +5202,7 @@ "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} } }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", "dependsOn": [ "identity", "identityRoleAssignments" @@ -6028,7 +5261,7 @@ }, "uploadSettings": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "Microsoft.FinOpsHubs.Core_Storage.UpdateSettings", "properties": { "expressionEvaluationOptions": { @@ -6037,22 +5270,19 @@ "mode": "Incremental", "parameters": { "app": { - "value": "[parameters('app')]" + "value": "[reference('appRegistration').outputs.app.value]" }, "identityName": { "value": "[reference('configContainer').outputs.identityName.value]" }, "scriptName": { - "value": "[format('{0}_uploadSettings', parameters('app').storage)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" + "value": "[format('{0}_uploadSettings', reference('appRegistration').outputs.app.value.storage)]" }, "environmentVariables": { "value": [ { "name": "ftkVersion", - "value": "[variables('finOpsToolkitVersion')]" + "value": "[variables('$fxv#1')]" }, { "name": "scopes", @@ -6076,13 +5306,16 @@ }, { "name": "storageAccountName", - "value": "[parameters('app').storage]" + "value": "[reference('appRegistration').outputs.app.value.storage]" }, { "name": "containerName", "value": "config" } ] + }, + "scriptContent": { + "value": "[variables('$fxv#2')]" } }, "template": { @@ -6092,8 +5325,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } }, "definitions": { @@ -6226,9 +5459,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -6255,7 +5485,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -6289,21 +5518,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -6312,21 +5555,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -6380,8 +5624,7 @@ }, "variables": { "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" }, "resources": { "identity": { @@ -6430,7 +5673,7 @@ "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} } }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", "dependsOn": [ "identity", "identityRoleAssignments" @@ -6446,55 +5689,65 @@ } }, "outputs": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Properties of the hub app." - }, - "value": "[parameters('app')]" - }, "dataFactoryName": { "type": "string", "metadata": { "description": "Name of the Data Factory." }, - "value": "[parameters('app').dataFactory]" + "value": "[reference('appRegistration').outputs.app.value.dataFactory]" }, "storageAccountName": { "type": "string", "metadata": { "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." }, - "value": "[parameters('app').storage]" + "value": "[reference('appRegistration').outputs.app.value.storage]" + }, + "configContainer": { + "type": "string", + "metadata": { + "description": "The name of the container used for configuration settings." + }, + "value": "[reference('configContainer').outputs.containerName.value]" + }, + "ingestionContainer": { + "type": "string", + "metadata": { + "description": "The name of the container used for normalized data ingestion." + }, + "value": "[reference('ingestionContainer').outputs.containerName.value]" }, "storageUrlForPowerBI": { "type": "string", "metadata": { "description": "URL to use when connecting custom Power BI reports to your data." }, - "value": "[format('https://{0}.dfs.{1}/{2}', parameters('app').storage, environment().suffixes.storage, variables('INGESTION'))]" + "value": "[format('https://{0}.dfs.{1}/{2}', reference('appRegistration').outputs.app.value.storage, environment().suffixes.storage, reference('ingestionContainer').outputs.containerName.value)]" }, "principalId": { "type": "string", "metadata": { "description": "Object ID of the Data Factory managed identity. This will be needed when configuring managed exports." }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + "value": "[reference('appRegistration').outputs.principalId.value]" }, - "triggerManagerIdentityName": { - "type": "string", + "publisherTags": { + "type": "object", "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." + "description": "Tags for the FinOps hub publisher." }, - "value": "[reference('appRegistration').outputs.triggerManagerIdentityName.value]" + "value": "[reference('appRegistration').outputs.app.value.publisher.tags]" } } } - } + }, + "dependsOn": [ + "infrastructure" + ] }, "cmExports": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", + "apiVersion": "2022-09-01", "name": "Microsoft.CostManagement.Exports", "properties": { "expressionEvaluationOptions": { @@ -6502,8 +5755,8 @@ }, "mode": "Incremental", "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft.CostManagement', 'Exports')]" + "hub": { + "value": "[variables('hub')]" } }, "template": { @@ -6513,97 +5766,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "12146592525418089958" + "version": "0.36.177.2456", + "templateHash": "12652260421176486151" } }, "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, "_1.HubRoutingProperties": { "type": "object", "properties": { @@ -6636,9 +5803,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -6665,7 +5829,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -6673,7 +5836,7 @@ }, "description": "FinOps hub private network routing properties.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, @@ -6692,11 +5855,11 @@ "name": "Resource name.", "description": "Resource ID and name.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } }, - "HubAppProperties": { + "HubProperties": { "type": "object", "properties": { "id": { @@ -6705,1960 +5868,2174 @@ "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "location": { "type": "string" }, "tags": { "type": "object" }, - "dataFactory": { - "type": "string" + "tagsByResource": { + "type": "object" }, - "keyVault": { + "version": { "type": "string" }, - "storage": { - "type": "string" + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "sourceTemplate": "hub-types.bicep" } } } }, "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", + "hub": { + "$ref": "#/definitions/HubProperties", "metadata": { - "description": "Required. FinOps hub app getting deployed." + "description": "Required. FinOps hub instance to deploy the app to." } } }, "variables": { - "$fxv#0": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\"}\n },\n {\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\"}\n },\n {\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerId\"}\n },\n {\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionId\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionName\"}\n },\n {\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"Date\"}\n },\n {\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\"}\n },\n {\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\"}\n },\n {\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\"}\n },\n {\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\"}\n },\n {\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\"}\n },\n {\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\"}\n },\n {\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\"}\n },\n {\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectivePrice\"}\n },\n {\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Cost\"}\n },\n {\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\"}\n },\n {\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\"}\n },\n {\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceLocation\"}\n },\n {\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\"}\n },\n {\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedService\"}\n },\n {\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\"}\n },\n {\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo1\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo2\"}\n },\n {\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AdditionalInfo\"}\n },\n {\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSection\"}\n },\n {\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\"}\n },\n {\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\"}\n },\n {\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceGroup\"}\n },\n {\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\"}\n },\n {\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationName\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderId\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderName\"}\n },\n {\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferId\"}\n },\n {\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\n },\n {\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\"}\n },\n {\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\"}\n },\n {\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PlanName\"}\n },\n {\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeType\"}\n },\n {\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Frequency\"}\n },\n {\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherType\"}\n }\n ]\n }\n}\n", - "$fxv#1": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\"}\n },\n {\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\n },\n {\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\"}\n },\n {\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\"}\n },\n {\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerId\"}\n },\n {\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionId\"}\n },\n {\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubscriptionName\"}\n },\n {\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"Date\"}\n },\n {\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\"}\n },\n {\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\"}\n },\n {\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\"}\n },\n {\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\"}\n },\n {\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\"}\n },\n {\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\"}\n },\n {\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\"}\n },\n {\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\"}\n },\n {\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectivePrice\"}\n },\n {\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Cost\"}\n },\n {\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\"}\n },\n {\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\"}\n },\n {\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceLocation\"}\n },\n {\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\"}\n },\n {\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedService\"}\n },\n {\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\"}\n },\n {\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo1\"}\n },\n {\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceInfo2\"}\n },\n {\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AdditionalInfo\"}\n },\n {\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\"}\n },\n {\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSection\"}\n },\n {\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\"}\n },\n {\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\"}\n },\n {\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceGroup\"}\n },\n {\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\"}\n },\n {\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationName\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderId\"}\n },\n {\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductOrderName\"}\n },\n {\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferId\"}\n },\n {\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\n },\n {\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\"}\n },\n {\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\"}\n },\n {\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PlanName\"}\n },\n {\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeType\"}\n },\n {\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Frequency\"}\n },\n {\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherType\"}\n }\n ]\n }\n}\n", - "$fxv#10": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"SKU\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SKU\" }\n },\n {\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Location\" }\n },\n {\n \"source\": { \"name\": \"CostWithNoReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostWithNoReservedInstances\" }\n },\n {\n \"source\": { \"name\": \"FirstUsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"FirstUsageDate\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\n \"sink\": { \"name\": \"LookBackPeriod\" }\n },\n {\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"NetSavings\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"NetSavings\" }\n },\n {\n \"source\": { \"name\": \"NormalizedSize\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NormalizedSize\" }\n },\n {\n \"source\": { \"name\": \"RecommendedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantity\" }\n },\n {\n \"source\": { \"name\": \"RecommendedQuantityNormalized\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"Scope\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Scope\" }\n },\n {\n \"source\": { \"name\": \"SkuProperties\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuProperties\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"TotalCostWithReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TotalCostWithReservedInstances\" }\n }\n ]\n }\n}\n", - "$fxv#11": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"Cost With No ReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostWithNoReservedInstancesJson\" }\n },\n {\n \"source\": { \"name\": \"First UsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"FirstUsageDate\" }\n },\n {\n \"source\": { \"name\": \"Instance Flexibility Ratio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"Instance Flexibility Group\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Location\" }\n },\n {\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\n \"sink\": { \"name\": \"LookBackPeriod\" }\n },\n {\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"Net Savings\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NetSavingsJson\" }\n },\n {\n \"source\": { \"name\": \"Normalized Size\", \"type\": \"String\" },\n \"sink\": { \"name\": \"NormalizedSize\" }\n },\n {\n \"source\": { \"name\": \"Recommended Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantity\" }\n },\n {\n \"source\": { \"name\": \"Recommended Quantity Normalized\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"scope\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Scope\" }\n },\n {\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuName\" }\n },\n {\n \"source\": { \"name\": \"Sku Properties\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuProperties\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"Total Cost With ReservedInstances\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TotalCostWithReservedInstancesJson\" }\n }\n ]\n }\n}\n", - "$fxv#12": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountName\" }\n },\n {\n \"source\": { \"name\": \"AccountOwnerEmail\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AccountOwnerEmail\" }\n },\n {\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Amount\" }\n },\n {\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ArmSkuName\" }\n },\n {\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingFrequency\" }\n },\n {\n \"source\": { \"name\": \"BillingMonth\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingMonth\" }\n },\n {\n \"source\": { \"name\": \"CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CostCenter\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"CurrentEnrollmentId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CurrentEnrollmentId\" }\n },\n {\n \"source\": { \"name\": \"DepartmentName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"DepartmentName\" }\n },\n {\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Description\" }\n },\n {\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EventDate\" }\n },\n {\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EventType\" }\n },\n {\n \"source\": { \"name\": \"MonetaryCommitment\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MonetaryCommitment\" }\n },\n {\n \"source\": { \"name\": \"Overage\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Overage\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\n },\n {\n \"source\": { \"name\": \"PurchasingEnrollment\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingEnrollment\" }\n },\n {\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderName\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n }\n ]\n }\n}\n", - "$fxv#13": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Amount\" }\n },\n {\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ArmSkuName\" }\n },\n {\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingFrequency\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Description\" }\n },\n {\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EventDate\" }\n },\n {\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EventType\" }\n },\n {\n \"source\": { \"name\": \"Invoice\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Invoice\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\n },\n {\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\n },\n {\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"Quantity\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderName\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n }\n ]\n }\n}\n", - "$fxv#2": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationId\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceSubcategory\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuMeter\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceDetails\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AmortizationClass\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ServiceModel\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPlanName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", - "$fxv#3": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationId\" }\n },\n {\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceSubcategory\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuMeter\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceDetails\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AmortizationClass\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ServiceModel\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPlanName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", - "$fxv#4": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", - "$fxv#5": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeClass\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ConsumedQuantity\" }\n },\n {\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ConsumedUnit\" }\n },\n {\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedCost\" }\n },\n {\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionId\" }\n },\n {\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"RegionName\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountId\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", - "$fxv#6": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"AvailabilityZone\", \"type\": \"String\" },\n \"sink\": { \"name\": \"AvailabilityZone\" }\n },\n {\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BilledCost\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountType\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"BillingPeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeCategory\" }\n },\n {\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeDescription\" }\n },\n {\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeFrequency\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"ChargePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"ChargeSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ChargeSubcategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\n },\n {\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\n },\n {\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"EffectiveCost\" }\n },\n {\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\n },\n {\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListCost\" }\n },\n {\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ListUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingCategory\" }\n },\n {\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"PricingQuantity\" }\n },\n {\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PricingUnit\" }\n },\n {\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProviderName\" }\n },\n {\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PublisherName\" }\n },\n {\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Region\" }\n },\n {\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceId\" }\n },\n {\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceName\" }\n },\n {\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ResourceType\" }\n },\n {\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceName\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuPriceId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountId\" }\n },\n {\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountName\" }\n },\n {\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SubAccountType\" }\n },\n {\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Tags\" }\n },\n {\n \"source\": { \"name\": \"UsageQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UsageQuantity\" }\n },\n {\n \"source\": { \"name\": \"UsageUnit\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UsageUnit\" }\n },\n {\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountName\" }\n },\n {\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\n },\n {\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"x_ChargeId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ChargeId\" }\n },\n {\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\n },\n {\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CostCenter\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerId\" }\n },\n {\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_CustomerName\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\n },\n {\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandCost\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandCost\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandCostInUsd\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandCostInUsd\" }\n },\n {\n \"source\": { \"name\": \"x_OnDemandUnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_OnDemandUnitPrice\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\n },\n {\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\n },\n {\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\n },\n {\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingCurrency\" }\n },\n {\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherCategory\" }\n },\n {\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_PublisherId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerId\" }\n },\n {\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResellerName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\n },\n {\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_ResourceType\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\n },\n {\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDescription\" }\n },\n {\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuDetails\" }\n },\n {\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOfferId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderId\" }\n },\n {\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuOrderName\" }\n },\n {\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\n },\n {\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuRegion\" }\n },\n {\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTerm\" }\n },\n {\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\n \"sink\": { \"name\": \"x_SkuTier\" }\n }\n ]\n }\n}\n", - "$fxv#7": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"EnrollmentNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"EnrollmentNumber\" }\n },\n {\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterID\" }\n },\n {\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\" }\n },\n {\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterType\" }\n },\n {\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\" }\n },\n {\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\" }\n },\n {\n \"source\": { \"name\": \"SkuID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuID\" }\n },\n {\n \"source\": { \"name\": \"ProductID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductID\" }\n },\n {\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\" }\n },\n {\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\" }\n },\n {\n \"source\": { \"name\": \"PartNumber\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PartNumber\" }\n },\n {\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveStartDate\" }\n },\n {\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveEndDate\" }\n },\n {\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\" }\n },\n {\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BasePrice\" }\n },\n {\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MarketPrice\" }\n },\n {\n \"source\": { \"name\": \"CurrencyCode\", \"type\": \"String\" },\n \"sink\": { \"name\": \"CurrencyCode\" }\n },\n {\n \"source\": { \"name\": \"IncludedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"IncludedQuantity\" }\n },\n {\n \"source\": { \"name\": \"OfferID\", \"type\": \"String\" },\n \"sink\": { \"name\": \"OfferID\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PriceType\" }\n }\n ]\n }\n}\n", - "$fxv#8": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountId\" }\n },\n {\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingAccountName\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileId\" }\n },\n {\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingProfileName\" }\n },\n {\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ServiceFamily\" }\n },\n {\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Product\" }\n },\n {\n \"source\": { \"name\": \"ProductId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ProductId\" }\n },\n {\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuId\" }\n },\n {\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\n \"sink\": { \"name\": \"UnitOfMeasure\" }\n },\n {\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterId\" }\n },\n {\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterName\" }\n },\n {\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterType\" }\n },\n {\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterSubCategory\" }\n },\n {\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\n \"sink\": { \"name\": \"MeterRegion\" }\n },\n {\n \"source\": { \"name\": \"TierMinimumUnits\", \"type\": \"String\" },\n \"sink\": { \"name\": \"TierMinimumUnits\" }\n },\n {\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveStartDate\" }\n },\n {\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"EffectiveEndDate\" }\n },\n {\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UnitPrice\" }\n },\n {\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"BasePrice\" }\n },\n {\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"MarketPrice\" }\n },\n {\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Currency\" }\n },\n {\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\n \"sink\": { \"name\": \"BillingCurrency\" }\n },\n {\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Term\" }\n },\n {\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\n \"sink\": { \"name\": \"PriceType\" }\n }\n ]\n }\n}\n", - "$fxv#9": "{\n \"additionalColumns\": [],\n \"translator\": {\n \"type\": \"TabularTranslator\",\n \"mappings\": [\n {\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\n },\n {\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\n },\n {\n \"source\": { \"name\": \"InstanceId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"InstanceId\" }\n },\n {\n \"source\": { \"name\": \"Kind\", \"type\": \"String\" },\n \"sink\": { \"name\": \"Kind\" }\n },\n {\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationOrderId\" }\n },\n {\n \"source\": { \"name\": \"ReservationId\", \"type\": \"String\" },\n \"sink\": { \"name\": \"ReservationId\" }\n },\n {\n \"source\": { \"name\": \"ReservedHours\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"ReservedHours\" }\n },\n {\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\n \"sink\": { \"name\": \"SkuName\" }\n },\n {\n \"source\": { \"name\": \"TotalReservedQuantity\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"TotalReservedQuantity\" }\n },\n {\n \"source\": { \"name\": \"UsageDate\", \"type\": \"DateTimeOffset\" },\n \"sink\": { \"name\": \"UsageDate\" }\n },\n {\n \"source\": { \"name\": \"UsedHours\", \"type\": \"Decimal\" },\n \"sink\": { \"name\": \"UsedHours\" }\n }\n ]\n }\n}\n", - "CONFIG": "config", - "INGESTION": "ingestion", - "MSEXPORTS": "msexports", - "ingestionIdFileNameSeparator": "__", - "finOpsToolkitVersion": "12.0" + "$fxv#0": "12.0", + "$fxv#1": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"Date\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectivePrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Cost\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceLocation\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedService\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo1\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo2\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AdditionalInfo\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSection\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceGroup\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PlanName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeType\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Frequency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherType\"}\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#10": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Kind\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Kind\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservedHours\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ReservedHours\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TotalReservedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"TotalReservedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"UsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsedHours\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UsedHours\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#11": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"SKU\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SKU\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Location\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CostWithNoReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostWithNoReservedInstances\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"FirstUsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"FirstUsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityRatio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InstanceFlexibilityGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"LookBackPeriod\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"NetSavings\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"NetSavings\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"NormalizedSize\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NormalizedSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RecommendedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RecommendedQuantityNormalized\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Scope\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Scope\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuProperties\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuProperties\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TotalCostWithReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TotalCostWithReservedInstances\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#12": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"Cost With No ReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostWithNoReservedInstancesJson\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"First UsageDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"FirstUsageDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Instance Flexibility Ratio\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityRatio\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Instance Flexibility Group\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InstanceFlexibilityGroup\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Location\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Location\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"LookBackPeriod\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"LookBackPeriod\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Net Savings\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NetSavingsJson\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Normalized Size\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"NormalizedSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Recommended Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Recommended Quantity Normalized\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"RecommendedQuantityNormalized\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"scope\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Scope\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Sku Properties\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuProperties\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Total Cost With ReservedInstances\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TotalCostWithReservedInstancesJson\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#13": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"AccountOwnerEmail\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerEmail\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Amount\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ArmSkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingMonth\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingMonth\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CurrentEnrollmentId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CurrentEnrollmentId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"DepartmentName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"DepartmentName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Description\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EventDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EventType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MonetaryCommitment\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MonetaryCommitment\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Overage\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Overage\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingEnrollment\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingEnrollment\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#14": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"Amount\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Amount\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ArmSkuName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ArmSkuName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Description\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Description\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EventDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EventType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EventType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Invoice\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Invoice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionGuid\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionGuid\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PurchasingSubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PurchasingSubscriptionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ReservationOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#2": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\":{\"name\":\"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStartDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingPeriodEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEndDate\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountOwnerId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AccountName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"SubscriptionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubscriptionName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Date\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"Date\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Quantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Quantity\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"EffectivePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectivePrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Cost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"Cost\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceLocation\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceLocation\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ConsumedService\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedService\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo1\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo1\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ServiceInfo2\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceInfo2\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"AdditionalInfo\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AdditionalInfo\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSectionId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"InvoiceSection\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceSection\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CostCenter\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ResourceGroup\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceGroup\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ReservationName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ReservationName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ProductOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductOrderName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"OfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferId\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"IsAzureCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"IsAzureCreditEligible\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PlanName\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"ChargeType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeType\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"Frequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Frequency\"}\r\n },\r\n {\r\n \"source\":{\"name\":\"PublisherType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherType\"}\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#3": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuMeter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AmortizationClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ServiceModel\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPlanName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#4": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CapacityReservationStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CapacityReservationStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuMeter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuMeter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AmortizationClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AmortizationClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServiceModel\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ServiceModel\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPlanName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPlanName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#5": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#6": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeClass\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeClass\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountStatus\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountStatus\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ConsumedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ConsumedUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ConsumedUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ContractedUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ContractedUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"RegionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"RegionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ContractedCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ContractedCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ListCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_ListCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#7": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"AvailabilityZone\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"AvailabilityZone\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BilledCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BilledCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingPeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"BillingPeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeFrequency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeFrequency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"ChargePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ChargeSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ChargeSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CommitmentDiscountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CommitmentDiscountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"EffectiveCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"InvoiceIssuerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"InvoiceIssuerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ListUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"ListUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"PricingQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PricingUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PricingUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProviderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProviderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PublisherName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PublisherName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Region\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Region\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuPriceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuPriceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SubAccountType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SubAccountType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Tags\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Tags\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UsageQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UsageUnit\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UsageUnit\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_AccountOwnerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_AccountOwnerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BilledUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BilledUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingExchangeRateDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_BillingExchangeRateDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ChargeId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ChargeId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostAllocationRuleName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostAllocationRuleName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CostCenter\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CostCenter\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_CustomerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_CustomerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_EffectiveUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_EffectiveUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceIssuerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceIssuerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_InvoiceSectionName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_InvoiceSectionName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandCost\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandCost\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandCostInUsd\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandCostInUsd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_OnDemandUnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_OnDemandUnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditApplied\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditApplied\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PartnerCreditRate\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PartnerCreditRate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingBlockSize\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"x_PricingBlockSize\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PricingUnitDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PricingUnitDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_PublisherId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_PublisherId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResellerName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResellerName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceGroupName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceGroupName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ResourceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_ResourceType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodEnd\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodEnd\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_ServicePeriodStart\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"x_ServicePeriodStart\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDescription\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDescription\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuDetails\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuDetails\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuIsCreditEligible\", \"type\": \"Boolean\" },\r\n \"sink\": { \"name\": \"x_SkuIsCreditEligible\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuMeterSubcategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuMeterSubcategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOfferId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOfferId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuOrderName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuOrderName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuPartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuPartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTerm\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTerm\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"x_SkuTier\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"x_SkuTier\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#8": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"EnrollmentNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"EnrollmentNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProductID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PartNumber\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PartNumber\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveStartDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveEndDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BasePrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MarketPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"CurrencyCode\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"CurrencyCode\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"IncludedQuantity\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"IncludedQuantity\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"OfferID\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"OfferID\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PriceType\" }\r\n }\r\n ]\r\n }\r\n}\r\n", + "$fxv#9": "{\r\n \"additionalColumns\": [],\r\n \"translator\": {\r\n \"type\": \"TabularTranslator\",\r\n \"mappings\": [\r\n {\r\n \"source\": { \"name\": \"BillingAccountId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingAccountName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingAccountName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingProfileName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingProfileName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ServiceFamily\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ServiceFamily\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Product\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Product\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"ProductId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"ProductId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"SkuId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"SkuId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitOfMeasure\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"UnitOfMeasure\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterId\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterId\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterName\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterName\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterType\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterSubCategory\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterSubCategory\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MeterRegion\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"MeterRegion\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"TierMinimumUnits\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"TierMinimumUnits\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveStartDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveStartDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"EffectiveEndDate\", \"type\": \"DateTimeOffset\" },\r\n \"sink\": { \"name\": \"EffectiveEndDate\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"UnitPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"UnitPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BasePrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"BasePrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"MarketPrice\", \"type\": \"Decimal\" },\r\n \"sink\": { \"name\": \"MarketPrice\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Currency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Currency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"BillingCurrency\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"BillingCurrency\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"Term\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"Term\" }\r\n },\r\n {\r\n \"source\": { \"name\": \"PriceType\", \"type\": \"String\" },\r\n \"sink\": { \"name\": \"PriceType\" }\r\n }\r\n ]\r\n }\r\n}\r\n" }, "resources": { - "dataFactory::linkedService_storageAccount": { - "existing": true, - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_config": { - "existing": true, - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]", - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_ingestion": { - "existing": true, - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('INGESTION'))]", - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_ingestion_files": { - "existing": true, - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', variables('INGESTION')))]", - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_manifest": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'manifest')]", + "appRegistration": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.CostManagement.Exports_Register", "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", "parameters": { - "fileName": { - "type": "String", - "defaultValue": "manifest.json" + "hub": { + "value": "[parameters('hub')]" }, - "folderPath": { - "type": "String", - "defaultValue": "[variables('MSEXPORTS')]" + "publisher": { + "value": "Microsoft FinOps hubs" + }, + "namespace": { + "value": "Microsoft.FinOpsHubs" + }, + "appName": { + "value": "Core" + }, + "displayName": { + "value": "FinOps hub core" + }, + "appVersion": { + "value": "[variables('$fxv#0')]" + }, + "features": { + "value": [ + "DataFactory", + "Storage" + ] } }, - "type": "Json", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().fileName}", - "type": "Expression" - }, - "folderPath": { - "value": "@{dataset().folderPath}", - "type": "Expression" - } - } - }, - "linkedServiceName": { - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_msexports": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, replace(format('{0}', variables('MSEXPORTS')), '-', '_'))]", - "properties": { - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" - }, - "fileSystem": "[reference('exportContainer').outputs.containerName.value]" - }, - "columnDelimiter": ",", - "escapeChar": "\"", - "quoteChar": "\"", - "firstRowAsHeader": true - }, - "linkedServiceName": { - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "appRegistration", - "exportContainer" - ] - }, - "dataFactory::dataset_msexports_gzip": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_gzip', variables('MSEXPORTS')))]", - "properties": { - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" - }, - "fileSystem": "[variables('MSEXPORTS')]" - }, - "columnDelimiter": ",", - "escapeChar": "\"", - "quoteChar": "\"", - "firstRowAsHeader": true, - "compressionCodec": "Gzip" - }, - "linkedServiceName": { - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::dataset_msexports_parquet": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_parquet', variables('MSEXPORTS')))]", - "properties": { - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" - }, - "fileSystem": "[variables('MSEXPORTS')]" - } - }, - "linkedServiceName": { - "referenceName": "[parameters('app').storage]", - "type": "LinkedServiceReference" - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "dataFactory::pipeline_ExecuteExportsETL": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ExecuteETL', variables('MSEXPORTS')))]", - "properties": { - "activities": [ - { - "name": "Wait", - "description": "Files may not be available immediately after being created.", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 60 + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "15179190433979236138" } }, - { - "name": "Read Manifest", - "description": "Load the export manifest to determine the scope, dataset, and date range.", - "type": "Lookup", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Completed" - ] + "definitions": { + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "formatSettings": { - "type": "JsonReadSettings" + "name": { + "type": "string" } }, - "dataset": { - "referenceName": "manifest", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@pipeline().parameters.fileName", - "type": "Expression" - }, - "folderPath": { - "value": "@pipeline().parameters.folderPath", - "type": "Expression" - } + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } - } - }, - { - "name": "Set Has No Rows", - "description": "Check the row count ", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "hasNoRows", - "value": { - "value": "@or(equals(activity('Read Manifest').output.firstRow.blobCount, null), equals(activity('Read Manifest').output.firstRow.blobCount, 0))", - "type": "Expression" - } - } - }, - { - "name": "Set Export Dataset Type", - "description": "Save the dataset type from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "exportDatasetType", - "value": { - "value": "@activity('Read Manifest').output.firstRow.exportConfig.type", - "type": "Expression" - } - } - }, - { - "name": "Set MCA Column", - "description": "Determines if the dataset schema has channel-specific columns and saves the column name that only exists in MCA to determine if it is an MCA dataset.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Succeeded" - ] + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "mcaColumnToCheck", - "value": { - "value": "@if(contains(createArray('pricesheet', 'reservationtransactions'), toLower(variables('exportDatasetType'))), 'BillingProfileId', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Net Savings', null))", - "type": "Expression" - } - } - }, - { - "name": "Set Export Dataset Version", - "description": "Save the dataset version from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "exportDatasetVersion", - "value": { - "value": "@activity('Read Manifest').output.firstRow.exportConfig.dataVersion", - "type": "Expression" + "HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } } }, - { - "name": "Detect Channel", - "description": "Determines what channel this export is from. Switch statement handles the different file types if the mcaColumnToCheck variable is set.", - "type": "Switch", - "dependsOn": [ - { - "activity": "Set Has No Rows", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set MCA Column", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Export Dataset Version", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "on": { - "value": "@if(or(empty(variables('mcaColumnToCheck')), variables('hasNoRows')), 'ignore', last(array(split(activity('Read Manifest').output.firstRow.blobs[0].blobName, '.'))))", - "type": "Expression" - }, - "cases": [ - { - "value": "csv", - "activities": [ + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppTags": { + "parameters": [ { - "name": "Check for MCA Column in CSV", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" - } - }, - "dataset": { - "referenceName": "[replace(format('{0}', variables('MSEXPORTS')), '-', '_')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" - } - } - } - } + "$ref": "#/definitions/HubAppProperties", + "name": "app" }, { - "name": "Set Schema File with Channel in CSV", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in CSV", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in CSV').output, 'firstRow'), contains(activity('Check for MCA Column in CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" - } - } + "type": "string", + "name": "resourceType" + }, + { + "type": "bool", + "nullable": true, + "name": "forceAppTags" } - ] + ], + "output": { + "type": "object", + "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - { - "value": "gz", - "activities": [ + "getPublisherTags": { + "parameters": [ { - "name": "Check for MCA Column in Gzip CSV", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" - } - }, - "dataset": { - "referenceName": "[format('{0}_gzip', variables('MSEXPORTS'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" - } - } - } - } + "$ref": "#/definitions/HubAppProperties", + "name": "app" }, { - "name": "Set Schema File with Channel in Gzip CSV", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in Gzip CSV", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Gzip CSV').output, 'firstRow'), contains(activity('Check for MCA Column in Gzip CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" - } - } + "type": "string", + "name": "resourceType" } - ] + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - { - "value": "parquet", - "activities": [ + "newApp": { + "parameters": [ { - "name": "Check for MCA Column in Parquet", - "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "ParquetSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" - } - }, - "dataset": { - "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", - "type": "Expression" - } - } - } - } + "$ref": "#/definitions/HubProperties", + "name": "hub" }, { - "name": "Set Schema File with Channel for Parquet", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check for MCA Column in Parquet", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Parquet').output, 'firstRow'), contains(activity('Check for MCA Column in Parquet').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", - "type": "Expression" - } - } + "type": "string", + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "appPartialName" + }, + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" } - ] - } - ], - "defaultActivities": [ - { - "name": "Set Schema File", - "type": "SetVariable", - "dependsOn": [], - "policy": { - "secureOutput": false, - "secureInput": false + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" }, - "userProperties": [], - "typeProperties": { - "variableName": "schemaFile", - "value": { - "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), '.json'))", - "type": "Expression" + "metadata": { + "description": "Creates a new FinOps hub app configuration object.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } } - ] - } - }, - { - "name": "Set Scope", - "description": "Save the scope from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "scope", - "value": { - "value": "@split(toLower(activity('Read Manifest').output.firstRow.exportConfig.resourceId), '/providers/microsoft.costmanagement/exports/')[0]", - "type": "Expression" + { + "namespace": "_1", + "members": { + "newAppInternal": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherSuffix" + }, + { + "type": "object", + "name": "publisherTags" + }, + { + "type": "string", + "name": "appName" + }, + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" + } + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": { + "name": "[parameters('appName')]", + "displayName": "[parameters('appDisplayName')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", + "publisher": { + "name": "[parameters('publisherName')]", + "displayName": "[parameters('publisherDisplayName')]", + "suffix": "[parameters('publisherSuffix')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" + }, + "hub": "[parameters('hub')]", + "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "safeStorageName": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "string", + "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } } } - }, - { - "name": "Set Date", - "description": "Save the exported month from the export manifest.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Succeeded" - ] + ], + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "Required. FinOps hub instance properties." } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "date", - "value": { - "value": "@replace(substring(activity('Read Manifest').output.firstRow.runInfo.startDate, 0, 7), '-', '')", - "type": "Expression" + "publisher": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app publisher." + } + }, + "namespace": { + "type": "string", + "metadata": { + "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "appName": { + "type": "string", + "metadata": { + "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app." + } + }, + "appVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Version number of the FinOps hub app." + } + }, + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." + } + }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." } } }, - { - "name": "Failed to Read Manifest", - "type": "Fail", - "dependsOn": [ - { - "activity": "Set Date", - "dependencyConditions": [ - "Failed" + "variables": { + "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", + "version": "[parameters('appVersion')]" + } + }, + "resources": [] + } + }, + "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" + }, + "resources": { + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', variables('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } ] }, - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Failed" + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" + } + } ] }, - { - "activity": "Set Scope", - "dependencyConditions": [ - "Failed" + "dependsOn": [ + "dfsEndpoint" + ] + }, + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } ] }, - { - "activity": "Read Manifest", - "dependencyConditions": [ - "Failed" - ] + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" + }, + "registrationEnabled": false }, - { - "activity": "Set Export Dataset Version", - "dependencyConditions": [ - "Failed" + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } ] }, - { - "activity": "Detect Channel", - "dependencyConditions": [ - "Failed" - ] + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('hub').options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[variables('app').dataFactory]", + "location": "[variables('app').hub.location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" + } } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Failed to read the manifest file for this export run. Manifest path: ', pipeline().parameters.folderPath)", - "type": "Expression" + }, + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[variables('app').storage]", + "location": "[parameters('hub').location]", + "sku": { + "name": "[parameters('hub').options.storageSku]" }, - "errorCode": "ManifestReadFailed" - } - }, - { - "name": "Check Schema", - "description": "Verify that the schema file exists in storage.", - "type": "GetMetadata", - "dependsOn": [ - { - "activity": "Set Scope", - "dependencyConditions": [ - "Succeeded" + "kind": "BlockBlobStorage", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" + }, + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + }, + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('hub').routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", + "groupIds": [ + "blob" + ] + } + } ] }, - { - "activity": "Set Date", - "dependencyConditions": [ - "Succeeded" + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('hub').routing.subnets.storage]" + }, + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", + "groupIds": [ + "dfs" + ] + } + } ] }, - { - "activity": "Detect Channel", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "dependsOn": [ + "storageAccount" + ] }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('schemaFile')", - "type": "Expression" - }, - "folderPath": "[format('{0}/schemas', reference('schemaFiles').outputs.containerName.value)]" + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[variables('app').keyVault]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", + "properties": { + "sku": { + "name": "[parameters('hub').options.keyVaultSku]", + "family": "A" + }, + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" } }, - "fieldList": [ - "exists" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - } - }, - { - "name": "Schema Not Found", - "type": "Fail", - "dependsOn": [ - { - "activity": "Check Schema", - "dependencyConditions": [ - "Failed" + "dependsOn": [ + "dataFactory" + ] + }, + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', variables('app').keyVault)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('hub').routing.subnets.keyVault]" + }, + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('The ', variables('schemaFile'), ' schema mapping file was not found. Please confirm version ', variables('exportDatasetVersion'), ' of the ', variables('exportDatasetType'), ' dataset is supported by this version of FinOps hubs. You may need to upgrade to a newer release. To add support for another dataset, you can create a custom mapping file.')", - "type": "Expression" }, - "errorCode": "SchemaNotFound" + "dependsOn": [ + "keyVault" + ] } }, - { - "name": "Set Hub Dataset", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Export Dataset Type", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false + "outputs": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "FinOps hub app configuration." + }, + "value": "[variables('app')]" }, - "userProperties": [], - "typeProperties": { - "variableName": "hubDataset", - "value": { - "value": "@if(equals(toLower(variables('exportDatasetType')), 'focuscost'), 'Costs', if(equals(toLower(variables('exportDatasetType')), 'pricesheet'), 'Prices', if(equals(toLower(variables('exportDatasetType')), 'reservationdetails'), 'CommitmentDiscountUsage', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Recommendations', if(equals(toLower(variables('exportDatasetType')), 'reservationtransactions'), 'Transactions', if(equals(toLower(variables('exportDatasetType')), 'actualcost'), 'ActualCosts', if(equals(toLower(variables('exportDatasetType')), 'amortizedcost'), 'AmortizedCosts', toLower(variables('exportDatasetType')))))))))", - "type": "Expression" - } + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" } - }, - { - "name": "Set Destination Folder", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Check Schema", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Hub Dataset", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "destinationFolder", - "value": { - "value": "@replace(concat(variables('hubDataset'),'/',substring(variables('date'), 0, 4),'/',substring(variables('date'), 4, 2),'/',toLower(variables('scope')), if(equals(variables('hubDataset'), 'Recommendations'), activity('Read Manifest').output.firstRow.exportConfig.exportName, '')),'//','/')", - "type": "Expression" - } + } + } + } + }, + "schemaFiles": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.CostManagement.Exports_Storage.SchemaFiles", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('appRegistration').outputs.app.value]" + }, + "container": { + "value": "config" + }, + "files": { + "value": { + "schemas/actualcost_c360-2025-04.json": "[variables('$fxv#1')]", + "schemas/amortizedcost_c360-2025-04.json": "[variables('$fxv#2')]", + "schemas/focuscost_1.2.json": "[variables('$fxv#3')]", + "schemas/focuscost_1.2-preview.json": "[variables('$fxv#4')]", + "schemas/focuscost_1.0r2.json": "[variables('$fxv#5')]", + "schemas/focuscost_1.0.json": "[variables('$fxv#6')]", + "schemas/focuscost_1.0-preview(v1).json": "[variables('$fxv#7')]", + "schemas/pricesheet_2023-05-01_ea.json": "[variables('$fxv#8')]", + "schemas/pricesheet_2023-05-01_mca.json": "[variables('$fxv#9')]", + "schemas/reservationdetails_2023-03-01.json": "[variables('$fxv#10')]", + "schemas/reservationrecommendations_2023-05-01_ea.json": "[variables('$fxv#11')]", + "schemas/reservationrecommendations_2023-05-01_mca.json": "[variables('$fxv#12')]", + "schemas/reservationtransactions_2023-05-01_ea.json": "[variables('$fxv#13')]", + "schemas/reservationtransactions_2023-05-01_mca.json": "[variables('$fxv#14')]" + } + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "13960345490822271084" } }, - { - "name": "For Each Blob", - "description": "Loop thru each exported file listed in the manifest.", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Set Destination Folder", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(variables('hasNoRows'), json('[]'), activity('Read Manifest').output.firstRow.blobs)", - "type": "Expression" - }, - "batchCount": "[if(parameters('app').hub.options.privateRouting, 4, 30)]", - "isSequential": false, - "activities": [ - { - "name": "Execute", - "description": "Run the ingestion ETL pipeline.", - "type": "ExecutePipeline", - "dependsOn": [], - "policy": { - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_ETL_{1}', variables('MSEXPORTS'), variables('INGESTION'))]", - "type": "PipelineReference" + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "waitOnCompletion": true, - "parameters": { - "blobPath": { - "value": "@item().blobName", - "type": "Expression" - }, - "destinationFolder": { - "value": "@variables('destinationFolder')", - "type": "Expression" - }, - "destinationFile": { - "value": "@last(array(split(replace(replace(item().blobName, '.gz', ''), '.csv', '.parquet'), '/')))", - "type": "Expression" - }, - "ingestionId": { - "value": "@activity('Read Manifest').output.firstRow.runInfo.runId", - "type": "Expression" - }, - "schemaFile": { - "value": "@variables('schemaFile')", - "type": "Expression" - }, - "exportDatasetType": { - "value": "@variables('exportDatasetType')", - "type": "Expression" - }, - "exportDatasetVersion": { - "value": "@variables('exportDatasetVersion')", - "type": "Expression" - } + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } - } - ] - } - }, - { - "name": "Copy Manifest", - "description": "Copy the manifest to the ingestion container to trigger ADX ingestion", - "type": "Copy", - "dependsOn": [ - { - "activity": "For Each Blob", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "sink": { - "type": "JsonSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" }, - "formatSettings": { - "type": "JsonWriteSettings" + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } } }, - "enableStaging": false - }, - "inputs": [ - { - "referenceName": "manifest", - "type": "DatasetReference", - "parameters": { - "fileName": "manifest.json", - "folderPath": { - "value": "@pipeline().parameters.folderPath", - "type": "Expression" - } + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } - ], - "outputs": [ - { - "referenceName": "manifest", - "type": "DatasetReference", - "parameters": { - "fileName": "manifest.json", - "folderPath": { - "value": "[format('@concat(''{0}/'', variables(''destinationFolder''))', variables('INGESTION'))]", - "type": "Expression" - } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } - ] - } - ], - "parameters": { - "folderPath": { - "type": "string" - }, - "fileName": { - "type": "string" - } - }, - "variables": { - "date": { - "type": "String" - }, - "destinationFolder": { - "type": "String" - }, - "exportDatasetType": { - "type": "String" - }, - "exportDatasetVersion": { - "type": "String" - }, - "hasNoRows": { - "type": "Boolean" - }, - "hubDataset": { - "type": "String" - }, - "mcaColumnToCheck": { - "type": "String" - }, - "schemaFile": { - "type": "String" - }, - "scope": { - "type": "String" - } - }, - "annotations": [ - "New export" - ] - }, - "dependsOn": [ - "appRegistration", - "dataFactory::dataset_manifest", - "dataFactory::dataset_msexports", - "dataFactory::dataset_msexports_gzip", - "dataFactory::dataset_msexports_parquet", - "dataFactory::pipeline_ToIngestion", - "schemaFiles" - ] - }, - "dataFactory::pipeline_ToIngestion": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ETL_{1}', variables('MSEXPORTS'), variables('INGESTION')))]", - "properties": { - "activities": [ - { - "name": "Get Existing Parquet Files", - "description": "Get the previously ingested files so we can remove any older data. This is necessary to avoid data duplication in reports.", - "type": "GetMetadata", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[format('{0}_files', variables('INGESTION'))]", - "type": "DatasetReference", - "parameters": { - "folderPath": "@pipeline().parameters.destinationFolder" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" } }, - "fieldList": [ - "childItems" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "enablePartitionDiscovery": false + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } }, - "formatSettings": { - "type": "ParquetReadSettings" + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } } }, - { - "name": "Filter Out Current Exports", - "description": "Remove existing files from the current export so those files do not get deleted.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Get Existing Parquet Files", - "dependencyConditions": [ - "Completed" - ] + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app that storage is getting updated for." } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", - "type": "Expression" - }, - "condition": { - "value": "[format('@and(endswith(item().name, ''.parquet''), not(startswith(item().name, concat(pipeline().parameters.ingestionId, ''{0}''))))', variables('ingestionIdFileNameSeparator'))]", - "type": "Expression" + }, + "container": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage container to create or update." + } + }, + "files": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." + } + }, + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." } } }, - { - "name": "Load Schema Mappings", - "description": "Get schema mapping file to use for the CSV to parquet conversion.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" + }, + "resources": { + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", + "properties": { + "publicAccess": "None", + "metadata": {} + } }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false + "storageAccount::blobService": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('app').storage]" + }, + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Identity', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", + "mode": "Incremental", "parameters": { - "fileName": { - "value": "@toLower(pipeline().parameters.schemaFile)", - "type": "Expression" + "app": { + "value": "[parameters('app')]" }, - "folderPath": "[format('{0}/schemas', variables('CONFIG'))]" - } - } - } - }, - { - "name": "Failed to Load Schema", - "type": "Fail", - "dependsOn": [ - { - "activity": "Load Schema Mappings", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to load the ', pipeline().parameters.schemaFile, ' schema file. Please confirm the schema and version are supported for FinOps hubs ingestion. Unsupported files will remain in the msexports container.')", - "type": "Expression" - }, - "errorCode": "SchemaLoadFailed" - } - }, - { - "name": "Set Additional Columns", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Load Schema Mappings", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "additionalColumns", - "value": { - "value": "@intersection(array(json(concat('[{\"name\":\"x_SourceProvider\",\"value\":\"Microsoft\"},{\"name\":\"x_SourceName\",\"value\":\"Cost Management\"},{\"name\":\"x_SourceType\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"},{\"name\":\"x_SourceVersion\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"}'))), activity('Load Schema Mappings').output.firstRow.additionalColumns)", - "type": "Expression" - } - } - }, - { - "name": "For Each Old File", - "description": "Loop thru each of the existing files from previous exports.", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Convert to Parquet", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Filter Out Current Exports", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@activity('Filter Out Current Exports').output.Value", - "type": "Expression" - }, - "activities": [ - { - "name": "Delete Old Ingested File", - "description": "Delete the previously ingested files from older exports.", - "type": "Delete", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "identityName": { + "value": "[format('{0}_blobManager', parameters('app').storage)]" }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[variables('INGESTION')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@concat(pipeline().parameters.destinationFolder, '/', item().name)", - "type": "Expression" - } - } - }, - "enableLogging": false, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - } + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + }, + "roles": { + "value": [ + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" + ] } - } - ] - } - }, - { - "name": "Set Destination Path", - "type": "SetVariable", - "dependsOn": [], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "destinationPath", - "value": { - "value": "[format('@concat(pipeline().parameters.destinationFolder, ''/'', pipeline().parameters.ingestionId, ''{0}'', pipeline().parameters.destinationFile)', variables('ingestionIdFileNameSeparator'))]", - "type": "Expression" - } - } - }, - { - "name": "Convert to Parquet", - "description": "[format('Convert CSV to parquet and move the file to the {0} container.', variables('INGESTION'))]", - "type": "Switch", - "dependsOn": [ - { - "activity": "Set Destination Path", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Load Schema Mappings", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Additional Columns", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "on": { - "value": "@last(array(split(pipeline().parameters.blobPath, '.')))", - "type": "Expression" - }, - "cases": [ - { - "value": "csv", - "activities": [ - { - "name": "Convert CSV File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:10:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" - } - }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" - } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "4534337491931150093" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false, - "translator": { - "value": "@activity('Load Schema Mappings').output.firstRow.translator", - "type": "Expression" - } - }, - "inputs": [ - { - "referenceName": "[replace(format('{0}', variables('MSEXPORTS')), '-', '_')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } - } - ], - "outputs": [ - { - "referenceName": "[variables('INGESTION')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } } - ] - } - ] - }, - { - "value": "gz", - "activities": [ - { - "name": "Convert GZip CSV File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:10:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "DelimitedTextSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "DelimitedTextReadSettings" - } + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" - } + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false, - "translator": { - "value": "@activity('Load Schema Mappings').output.firstRow.translator", - "type": "Expression" + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } - }, - "inputs": [ - { - "referenceName": "[format('{0}_gzip', variables('MSEXPORTS'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } } - } - ], - "outputs": [ - { - "referenceName": "[variables('INGESTION')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" } } } - ] - } - ] - }, - { - "value": "parquet", - "activities": [ - { - "name": "Move Parquet File", - "type": "Copy", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "ParquetSource", - "additionalColumns": { - "value": "@variables('additionalColumns')", - "type": "Expression" - }, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" - } + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." }, - "sink": { - "type": "ParquetSink", - "storeSettings": { - "type": "AzureBlobFSWriteSettings" - }, - "formatSettings": { - "type": "ParquetWriteSettings", - "fileExtension": ".parquet" - } + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." }, - "enableStaging": false, - "parallelCopies": 1, - "validateDataConsistency": false + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } }, - "inputs": [ - { - "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" } } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" } - ], - "outputs": [ - { - "referenceName": "[variables('INGESTION')]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@variables('destinationPath')", - "type": "Expression" + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the identity is associated with." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the user assigned identity." + } + }, + "roleAssignmentResourceId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the resource access is being granted for." + } + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. List of RBAC role assignment GUIDs." + } + } + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "location": "[parameters('app').hub.location]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(parameters('roles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" ] } - ] - } - ], - "defaultActivities": [ - { - "name": "Unsupported File Type", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to ingest the specified export file because the file type is not supported. File: ', pipeline().parameters.blobPath)", - "type": "Expression" + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" }, - "errorCode": "UnsupportedExportFileType" + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." + }, + "value": "[parameters('identityName')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" + } } } - ] - } - }, - { - "name": "Read Hub Config", - "description": "Read the hub config to determine if the export should be retained.", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + } }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false + "uploadFiles": { + "condition": "[variables('hasFiles')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Upload', deployment().name)]", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", + "mode": "Incremental", "parameters": { - "fileName": "settings.json", - "folderPath": "[variables('CONFIG')]" - } - } - } - }, - { - "name": "If Not Retaining Exports", - "description": "If the msexports retention period <= 0, delete the source file. The main reason to keep the source file is to allow for troubleshooting and reprocessing in the future.", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Convert to Parquet", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Read Hub Config", - "dependencyConditions": [ - "Completed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@lessOrEquals(coalesce(activity('Read Hub Config').output.firstRow.retention.msexports.days, 0), 0)", - "type": "Expression" - }, - "ifTrueActivities": [ - { - "name": "Delete Source File", - "description": "Delete the exported data file to keep storage costs down. This file is not referenced by any reporting systems.", - "type": "Delete", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false + "app": { + "value": "[parameters('app')]" }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "[format('{0}_parquet', variables('MSEXPORTS'))]", - "type": "DatasetReference", - "parameters": { - "blobPath": { - "value": "@pipeline().parameters.blobPath", - "type": "Expression" - } + "identityName": { + "value": "[reference('identity').outputs.name.value]" + }, + "environmentVariables": { + "value": [ + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" + }, + { + "name": "files", + "value": "[string(parameters('files'))]" } - }, - "enableLogging": false, - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - } - } - } - ] - } - } - ], - "parameters": { - "blobPath": { - "type": "String" - }, - "destinationFile": { - "type": "string" - }, - "destinationFolder": { - "type": "string" - }, - "ingestionId": { - "type": "string" - }, - "schemaFile": { - "type": "string" - }, - "exportDatasetType": { - "type": "string" - }, - "exportDatasetVersion": { - "type": "string" - } - }, - "variables": { - "additionalColumns": { - "type": "Array" - }, - "destinationPath": { - "type": "String" - } - } - }, - "dependsOn": [ - "appRegistration", - "dataFactory::dataset_msexports", - "dataFactory::dataset_msexports_gzip", - "dataFactory::dataset_msexports_parquet" - ] - }, - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "dependsOn": [ - "appRegistration" - ] - }, - "appRegistration": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.Exports_Register", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "version": { - "value": "[variables('finOpsToolkitVersion')]" - }, - "features": { - "value": [ - "Storage", - "DataFactory" - ] - }, - "storageRoles": { - "value": [ - "18d7d88d-d35e-4fb5-a5c3-7773c20a72d9" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5436870138046688593" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } + ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" } }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } }, - "dataFactory": { - "type": "string" + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "keyVault": { - "type": "string" + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "scripts": { - "type": "string" + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } } }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" + }, + "filesUploaded": { + "type": "int", "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" }, - "HubAppFeature": { + "identityId": { "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" }, - "HubAppProperties": { + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + } + } + } + }, + "dependsOn": [ + "appRegistration" + ] + }, + "exportContainer": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.CostManagement.Exports_Storage.ExportContainer", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[reference('appRegistration').outputs.app.value]" + }, + "container": { + "value": "msexports" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "13960345490822271084" + } + }, + "definitions": { + "_1.HubProperties": { "type": "object", "properties": { "id": { @@ -8667,38 +8044,227 @@ "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "location": { "type": "string" }, "tags": { "type": "object" }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" + "tagsByResource": { + "type": "object" }, - "storage": { + "version": { "type": "string" }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, "hub": { "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -8706,1009 +8272,188 @@ } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app getting deployed." + "description": "Required. FinOps hub app that storage is getting updated for." } }, - "version": { + "container": { "type": "string", "metadata": { - "description": "Required. Version number of the FinOps hub app." - } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." + "description": "Required. Name of the storage container to create or update." } }, - "storageRoles": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], + "files": { + "type": "object", + "defaultValue": {}, "metadata": { - "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." } }, - "telemetryString": { - "type": "string", - "defaultValue": "", + "forceCreateBlobManagerIdentity": { + "type": "bool", + "defaultValue": false, "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." } } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0}', parameters('app').id)]", - "version": "[parameters('version')]" - } - }, - "resources": [] - } - }, - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", - "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Get storage context\r\n$storageContext = @{\r\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\r\n Container = $env:containerName\r\n}\r\n\r\n# Uploading files\r\n$files = $env:files | ConvertFrom-Json -Depth 10\r\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\r\n$files.PSObject.Properties | ForEach-Object {\r\n $filePath = $_.Name\r\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\r\n Write-Output \" Uploading $filePath...\"\r\n $_.Value | Out-File $tempPath\r\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\r\n}\r\n", + "fileCount": "[length(items(parameters('files')))]", + "hasFiles": "[greater(variables('fileCount'), 0)]" }, "resources": { - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", - "properties": { - "name": "[parameters('app').storage]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "storageAccount" - ] - }, - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", - "properties": { - "name": "[parameters('app').keyVault]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "keyVault" - ] - }, - "dataFactory::managedVirtualNetwork": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "properties": {}, - "dependsOn": [ - "dataFactory" - ] - }, - "dataFactory::managedIntegrationRuntime": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", - "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "default", - "type": "ManagedVirtualNetworkReference" - }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('app').hub.location]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 - } - } - } - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedVirtualNetwork" - ] - }, - "dataFactory::linkedService_keyVault": { - "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "keyVault" - ] - }, - "dataFactory::linkedService_storageAccount": { - "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", + "storageAccount::blobService::targetContainer": { + "type": "Microsoft.Storage/storageAccounts/blobServices/containers", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "storageAccount" - ] + "publicAccess": "None", + "metadata": {} + } }, "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", + "existing": true, "type": "Microsoft.Storage/storageAccounts/blobServices", "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] - }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] - }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] - }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] - }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] - }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" - } - } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] - }, - "appTelemetry": { - "condition": "[parameters('app').hub.options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", - "properties": "[variables('telemetryProps')]" - }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" - } - } - }, - "storageRoleAssignments": { - "copy": { - "name": "storageRoleAssignments", - "count": "[length(variables('factoryStorageRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "storageAccount" - ] - }, - "triggerManagerIdentity": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "dependsOn": [ - "dataFactory" - ] - }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "triggerManagerIdentity" - ] + "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" }, "storageAccount": { - "condition": "[variables('usesStorage')]", + "existing": true, "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "location": "[parameters('app').hub.location]", - "sku": { - "name": "[parameters('app').hub.options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + "name": "[parameters('app').storage]" }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "identity": { + "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Identity', deployment().name)]", "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "blob" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" - }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", - "properties": { - "sku": { - "name": "[parameters('app').hub.options.keyVaultSku]", - "family": "A" - }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" - } - }, - "dependsOn": [ - "dataFactory" - ] - }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} - }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('app').keyVault)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.keyVault]" - }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] - }, - "dependsOn": [ - "keyVault" - ] - }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "expressionEvaluationOptions": { + "scope": "inner" }, "mode": "Incremental", "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } + "app": { + "value": "[parameters('app')]" }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } + "identityName": { + "value": "[format('{0}_blobManager', parameters('app').storage)]" }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", - "getStoragePrivateEndpointConnections", - "keyVault" - ] - }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" + "roleAssignmentResourceId": { + "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + "roles": { + "value": [ + "ba92f5b4-2d11-453d-a403-e96b0029c9fe", + "69566ab7-960f-475b-8e7c-b3118f30c6bd" + ] } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" + "version": "0.36.177.2456", + "templateHash": "4534337491931150093" } }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "getKeyVaultPrivateEndpointConnections", - "keyVault" - ] - }, - "getStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", - "stopTriggers", - "storageAccount" - ] - }, - "approveStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "getStoragePrivateEndpointConnections", - "storageAccount" - ] - }, - "stopTriggers": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "arguments": { - "value": "-Stop" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('app').dataFactory]" - } - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", + "_1.HubRoutingProperties": { + "type": "object", "properties": { "networkId": { "type": "string" @@ -9739,9 +8484,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -9768,7 +8510,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -9802,21 +8543,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -9825,21 +8580,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -9847,513 +8603,152 @@ } } }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." + "description": "Required. FinOps hub app the identity is associated with." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Required. Name of the user assigned identity." } }, - "arguments": { + "roleAssignmentResourceId": { "type": "string", - "defaultValue": "", "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." + "description": "Required. Resource ID of the resource access is being granted for." } }, - "environmentVariables": { + "roles": { "type": "array", "items": { - "$ref": "#/definitions/EnvironmentVariable" + "type": "string" }, - "defaultValue": [], "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "description": "Required. List of RBAC role assignment GUIDs." } } }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, "resources": { "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "tags": "[__bicep.getPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", "location": "[parameters('app').hub.location]" }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" + "count": "[length(parameters('roles'))]" }, - "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] + } + }, + "outputs": { + "id": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity." + }, + "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } + "name": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity." }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "value": "[parameters('identityName')]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity." + }, + "value": "[reference('identity').principalId]" } } } - }, - "dependsOn": [ - "appTelemetry", - "dataFactory", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" - ] - } - }, - "outputs": { - "dataFactoryId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Factory instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" - }, - "keyVaultId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Key Vault instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" - }, - "storageAccountId": { - "type": "string", - "metadata": { - "description": "Resource ID of the storage account instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + } }, - "triggerManagerIdentityName": { - "type": "string", - "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." - }, - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - } - } - } - } - }, - "schemaFiles": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.Exports_Storage.SchemaFiles", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "container": { - "value": "config" - }, - "files": { - "value": { - "schemas/actualcost_c360-2025-04.json": "[variables('$fxv#0')]", - "schemas/amortizedcost_c360-2025-04.json": "[variables('$fxv#1')]", - "schemas/focuscost_1.2.json": "[variables('$fxv#2')]", - "schemas/focuscost_1.2-preview.json": "[variables('$fxv#3')]", - "schemas/focuscost_1.0r2.json": "[variables('$fxv#4')]", - "schemas/focuscost_1.0.json": "[variables('$fxv#5')]", - "schemas/focuscost_1.0-preview(v1).json": "[variables('$fxv#6')]", - "schemas/pricesheet_2023-05-01_ea.json": "[variables('$fxv#7')]", - "schemas/pricesheet_2023-05-01_mca.json": "[variables('$fxv#8')]", - "schemas/reservationdetails_2023-03-01.json": "[variables('$fxv#9')]", - "schemas/reservationrecommendations_2023-05-01_ea.json": "[variables('$fxv#10')]", - "schemas/reservationrecommendations_2023-05-01_mca.json": "[variables('$fxv#11')]", - "schemas/reservationtransactions_2023-05-01_ea.json": "[variables('$fxv#12')]", - "schemas/reservationtransactions_2023-05-01_mca.json": "[variables('$fxv#13')]" - } - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "7314877606184110283" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", + "uploadFiles": { + "condition": "[variables('hasFiles')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[format('{0}.Upload', deployment().name)]", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" + "expressionEvaluationOptions": { + "scope": "inner" }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." - } - }, - "container": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage container to create or update." - } - }, - "files": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." - } - }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" - }, - "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", - "properties": { - "publicAccess": "None", - "metadata": {} - } - }, - "storageAccount::blobService": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" - }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Identity', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "roles": { - "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" - ] - } + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[reference('identity').outputs.name.value]" + }, + "environmentVariables": { + "value": [ + { + "name": "storageAccountName", + "value": "[parameters('app').storage]" + }, + { + "name": "containerName", + "value": "[parameters('container')]" + }, + { + "name": "files", + "value": "[string(parameters('files'))]" + } + ] + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", @@ -10362,12 +8757,23 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2980528181281411934" + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } }, "definitions": { - "_1.HubProperties": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "_1.HubProperties": { "type": "object", "properties": { "id": { @@ -10485,9 +8891,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -10514,7 +8917,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -10548,21 +8950,35 @@ "HubAppProperties": { "type": "object", "properties": { - "id": { - "type": "string" - }, "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "displayName": { "type": "string" }, "tags": { "type": "object" }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, "dataFactory": { "type": "string" }, @@ -10571,21 +8987,22 @@ }, "storage": { "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", "dataFactory": "Name of the Data Factory instance for this publisher.", "keyVault": "Name of the KeyVault instance for this publisher.", "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", "description": "FinOps hub app configuration settings.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" @@ -10593,13354 +9010,9051 @@ } } }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], "parameters": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the identity is associated with." + "description": "Required. FinOps hub app the deployment script is being run for." } }, "identityName": { "type": "string", "metadata": { - "description": "Required. Name of the user assigned identity." + "description": "Required. Name of the managed identity to create." } }, - "roleAssignmentResourceId": { + "scriptName": { "type": "string", + "defaultValue": "[deployment().name]", "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." } }, - "roles": { + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/EnvironmentVariable" }, + "defaultValue": [], "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." + "description": "Optional. Environment variables to use for the deployment script." } } }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, "resources": { "identity": { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[parameters('identityName')]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", "location": "[parameters('app').hub.location]" }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, "identityRoleAssignments": { "copy": { "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" + "count": "[length(variables('privateEndpointDeploymentRoles'))]" }, + "condition": "[parameters('app').hub.options.privateRouting]", "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", "principalId": "[reference('identity').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "identity" ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } }, - "value": "[reference('identity').principalId]" + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] } } } - } + }, + "dependsOn": [ + "identity" + ] + } + }, + "outputs": { + "containerName": { + "type": "string", + "metadata": { + "description": "The name of the storage container." + }, + "value": "[parameters('container')]" }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Upload', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "containerName": { - "type": "string", - "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" - }, - "filesUploaded": { - "type": "int", - "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" - }, - "identityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" - }, - "identityPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" - } - } - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "exportContainer": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.Exports_Storage.ExportContainer", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "container": { - "value": "[variables('MSEXPORTS')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "7314877606184110283" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app that storage is getting updated for." - } - }, - "container": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage container to create or update." - } - }, - "files": { - "type": "object", - "defaultValue": {}, - "metadata": { - "description": "Optional. Dictionary of key/value pairs for the files to upload to the specified container. The key is the target path under the container and the value is the contents of the file. Default: {} (no files to upload)." - } - }, - "forceCreateBlobManagerIdentity": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Indicates whether to create the blob manager user assigned identity even if files are not being uploaded. Default: false." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\n# Get storage context\n$storageContext = @{\n Context = New-AzStorageContext -StorageAccountName $env:storageAccountName -UseConnectedAccount\n Container = $env:containerName\n}\n\n# Uploading files\n$files = $env:files | ConvertFrom-Json -Depth 10\nWrite-Output \"Uploading ${$files.PSObject.Properties.Count} files...\"\n$files.PSObject.Properties | ForEach-Object {\n $filePath = $_.Name\n $tempPath = \"./$($filePath -replace \"/\", \"_\")\"\n Write-Output \" Uploading $filePath...\"\n $_.Value | Out-File $tempPath\n Set-AzStorageBlobContent @storageContext -File $tempPath -Blob $filePath -Force | Out-Null\n}\n", - "fileCount": "[length(items(parameters('files')))]", - "hasFiles": "[greater(variables('fileCount'), 0)]" - }, - "resources": { - "storageAccount::blobService::targetContainer": { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}/{2}', parameters('app').storage, 'default', parameters('container'))]", - "properties": { - "publicAccess": "None", - "metadata": {} - } - }, - "storageAccount::blobService": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]" - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" - }, - "identity": { - "condition": "[or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Identity', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_blobManager', parameters('app').storage)]" - }, - "roleAssignmentResourceId": { - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "roles": { - "value": [ - "ba92f5b4-2d11-453d-a403-e96b0029c9fe", - "69566ab7-960f-475b-8e7c-b3118f30c6bd" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2980528181281411934" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the identity is associated with." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the user assigned identity." - } - }, - "roleAssignmentResourceId": { - "type": "string", - "metadata": { - "description": "Required. Resource ID of the resource access is being granted for." - } - }, - "roles": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. List of RBAC role assignment GUIDs." - } - } - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.ManagedIdentity/userAssignedIdentities')]", - "location": "[parameters('app').hub.location]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(parameters('roles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "name": "[guid(parameters('roleAssignmentResourceId'), parameters('roles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', parameters('roles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "id": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity." - }, - "value": "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName'))]" - }, - "name": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity." - }, - "value": "[parameters('identityName')]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity." - }, - "value": "[reference('identity').principalId]" - } - } - } - } - }, - "uploadFiles": { - "condition": "[variables('hasFiles')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.Upload', deployment().name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[reference('identity').outputs.name.value]" - }, - "environmentVariables": { - "value": [ - { - "name": "storageAccountName", - "value": "[parameters('app').storage]" - }, - { - "name": "containerName", - "value": "[parameters('container')]" - }, - { - "name": "files", - "value": "[string(parameters('files'))]" - } - ] - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "identity" - ] - } - }, - "outputs": { - "containerName": { - "type": "string", - "metadata": { - "description": "The name of the storage container." - }, - "value": "[parameters('container')]" - }, - "filesUploaded": { - "type": "int", - "metadata": { - "description": "The number of files uploaded to the storage container." - }, - "value": "[variables('fileCount')]" - }, - "identityId": { - "type": "string", - "metadata": { - "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" - }, - "identityPrincipalId": { - "type": "string", - "metadata": { - "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." - }, - "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" - } - } - } - }, - "dependsOn": [ - "appRegistration" - ] - }, - "trigger_ExportManifestAdded": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.Exports_ADF.ExportManifestTrigger", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('app').dataFactory]" - }, - "triggerName": { - "value": "[format('{0}_ManifestAdded', variables('MSEXPORTS'))]" - }, - "pipelineName": { - "value": "[format('{0}_ExecuteETL', variables('MSEXPORTS'))]" - }, - "pipelineParameters": { - "value": { - "folderPath": "@triggerBody().folderPath", - "fileName": "@triggerBody().fileName" - } - }, - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "storageContainer": { - "value": "[variables('MSEXPORTS')]" - }, - "storagePathEndsWith": { - "value": "manifest.json" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "14264521107451792604" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } - }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." - } - }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." - } - }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." - } - }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." - } - }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." - } - } - }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" - }, - "parameters": "[parameters('pipelineParameters')]" - } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] - } - } - } - ] - } - }, - "dependsOn": [ - "appRegistration", - "dataFactory::pipeline_ExecuteExportsETL" - ] - } - }, - "outputs": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Properties of the hub app." - }, - "value": "[parameters('app')]" - }, - "exportContainer": { - "type": "string", - "metadata": { - "description": "Name of the container used for Cost Management exports." - }, - "value": "[reference('exportContainer').outputs.containerName.value]" - }, - "schemaFilesUploaded": { - "type": "int", - "metadata": { - "description": "Number of schema files uploaded." - }, - "value": "[reference('schemaFiles').outputs.filesUploaded.value]" - } - } - } - }, - "dependsOn": [ - "core" - ] - }, - "cmManagedExports": { - "condition": "[parameters('enableManagedExports')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.ManagedExports", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft.CostManagement', 'ManagedExports')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "15949887161767442453" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getExportBody": { - "parameters": [ - { - "type": "string", - "name": "exportContainerName" - }, - { - "type": "string", - "name": "datasetType" - }, - { - "type": "string", - "name": "schemaVersion" - }, - { - "type": "bool", - "name": "isMonthly" - }, - { - "type": "string", - "name": "exportFormat" - }, - { - "type": "string", - "name": "compressionMode" - }, - { - "type": "string", - "name": "partitionData" - }, - { - "type": "string", - "name": "dataOverwriteBehavior" - } - ], - "output": { - "type": "string", - "value": "[format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}\", \"name\": \"@{{variables(''exportName'')}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'))]" - } - }, - "getExportBodyV2": { - "parameters": [ - { - "type": "string", - "name": "exportContainerName" - }, - { - "type": "string", - "name": "datasetType" - }, - { - "type": "bool", - "name": "isMonthly" - }, - { - "type": "string", - "name": "exportFormat" - }, - { - "type": "string", - "name": "compressionMode" - }, - { - "type": "string", - "name": "partitionData" - }, - { - "type": "string", - "name": "dataOverwriteBehavior" - }, - { - "type": "string", - "name": "recommendationScope" - }, - { - "type": "string", - "name": "recommendationLookbackPeriod" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "string", - "value": "[if(equals(toLower(parameters('datasetType')), 'focuscost'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{10}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), if(equals(toLower(parameters('datasetType')), 'reservationdetails'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(or(equals(toLower(parameters('datasetType')), 'pricesheet'), equals(toLower(parameters('datasetType')), 'reservationtransactions')), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}}}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], if(parameters('isMonthly'), 'TheCurrentMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(equals(toLower(parameters('datasetType')), 'reservationrecommendations'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [ {{ \"name\": \"reservationScope\", \"value\": \"{1}\" }}, {{ \"name\": \"resourceType\", \"value\": \"{2}\" }}, {{ \"name\": \"lookBackPeriod\", \"value\": \"{3}\" }}] }}}}, \"timeframe\": \"{4}\", \"type\": \"{5}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{6}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{7}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{8}\", \"partitionData\": \"{9}\", \"dataOverwriteBehavior\": \"{10}\", \"compressionMode\": \"{11}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{12}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{13}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', variables('exportDataVersions')[toLower(parameters('datasetType'))], parameters('recommendationScope'), parameters('resourceType'), parameters('recommendationLookbackPeriod'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), 'undefined'))))]" - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - } - }, - "variables": { - "CONFIG": "config", - "MSEXPORTS": "msexports", - "exportsApiVersion": "2023-07-01-preview", - "exportDataVersions": { - "focuscost": "1.2-preview", - "pricesheet": "2023-03-01", - "reservationdetails": "2023-03-01", - "reservationrecommendations": "2023-05-01", - "reservationtransactions": "2023-05-01" - }, - "finOpsToolkitVersion": "12.0" - }, - "resources": { - "dataFactory::dataset_config": { - "existing": true, - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('CONFIG'))]" - }, - "dataFactory::trigger_DailySchedule": { - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_DailySchedule', variables('CONFIG')))]", - "properties": { - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[format('{0}_StartExportProcess', variables('CONFIG'))]", - "type": "PipelineReference" - }, - "parameters": { - "Recurrence": "Daily" - } - } - ], - "type": "ScheduleTrigger", - "typeProperties": { - "recurrence": { - "frequency": "Hour", - "interval": 24, - "startTime": "2023-01-01T01:01:00", - "timeZone": "[reference('timeZones').outputs.Timezone.value]" - } - } - }, - "dependsOn": [ - "dataFactory::pipeline_StartExportProcess", - "timeZones" - ] - }, - "dataFactory::trigger_MonthlySchedule": { - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_MonthlySchedule', variables('CONFIG')))]", - "properties": { - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[format('{0}_StartExportProcess', variables('CONFIG'))]", - "type": "PipelineReference" - }, - "parameters": { - "Recurrence": "Monthly" - } - } - ], - "type": "ScheduleTrigger", - "typeProperties": { - "recurrence": { - "frequency": "Month", - "interval": 1, - "startTime": "2023-01-05T01:11:00", - "timeZone": "[reference('timeZones').outputs.Timezone.value]", - "schedule": { - "monthDays": [ - 2, - 5, - 19 - ] - } - } - } - }, - "dependsOn": [ - "dataFactory::pipeline_StartExportProcess", - "timeZones" - ] - }, - "dataFactory::pipeline_StartBackfillProcess": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_StartBackfillProcess', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('fileName')", - "type": "Expression" - }, - "folderPath": { - "value": "@variables('folderPath')", - "type": "Expression" - } - } - } - } - }, - { - "name": "Set backfill end date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "endDate", - "value": { - "value": "@addDays(startOfMonth(utcNow()), -1)", - "type": "Expression" - } - } - }, - { - "name": "Set backfill start date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "startDate", - "value": { - "value": "@subtractFromTime(startOfMonth(utcNow()), activity('Get Config').output.firstRow.retention.ingestion.months, 'Month')", - "type": "Expression" - } - } - }, - { - "name": "Set export start date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set backfill start date", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "thisMonth", - "value": { - "value": "@startOfMonth(variables('endDate'))", - "type": "Expression" - } - } - }, - { - "name": "Set export end date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set export start date", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "nextMonth", - "value": { - "value": "@startOfMonth(subtractFromTime(variables('thisMonth'), 1, 'Month'))", - "type": "Expression" - } - } - }, - { - "name": "Every Month", - "type": "Until", - "dependsOn": [ - { - "activity": "Set export end date", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set backfill end date", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@less(variables('thisMonth'), variables('startDate'))", - "type": "Expression" - }, - "activities": [ - { - "name": "Update export start date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Backfill data", - "dependencyConditions": [ - "Completed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "thisMonth", - "value": { - "value": "@variables('nextMonth')", - "type": "Expression" - } - } - }, - { - "name": "Update export end date", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Update export start date", - "dependencyConditions": [ - "Completed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "nextMonth", - "value": { - "value": "@subtractFromTime(variables('thisMonth'), 1, 'Month')", - "type": "Expression" - } - } - }, - { - "name": "Backfill data", - "type": "ExecutePipeline", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_RunBackfillJob', variables('CONFIG'))]", - "type": "PipelineReference" - }, - "waitOnCompletion": true, - "parameters": { - "StartDate": { - "value": "@variables('thisMonth')", - "type": "Expression" - }, - "EndDate": { - "value": "@addDays(addToTime(variables('thisMonth'), 1, 'Month'), -1)", - "type": "Expression" - } - } - } - } - ], - "timeout": "0.02:00:00" - } - } - ], - "concurrency": 1, - "variables": { - "exportName": { - "type": "String" - }, - "storageAccountId": { - "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "finOpsHub": { - "type": "String", - "defaultValue": "[parameters('app').hub.name]" - }, - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[variables('CONFIG')]" - }, - "endDate": { - "type": "String" - }, - "startDate": { - "type": "String" - }, - "thisMonth": { - "type": "String" - }, - "nextMonth": { - "type": "String" - } - } - }, - "dependsOn": [ - "dataFactory::pipeline_RunBackfillJob" - ] - }, - "dataFactory::pipeline_RunBackfillJob": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_RunBackfillJob', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('fileName')", - "type": "Expression" - }, - "folderPath": { - "value": "@variables('folderPath')", - "type": "Expression" - } - } - } - } - }, - { - "name": "Set Scopes", - "description": "Save scopes to test if it is an array", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@activity('Get Config').output.firstRow.scopes", - "type": "Expression" - } - } - }, - { - "name": "Set Scopes as Array", - "description": "Wraps a single scope object into an array to work around the PowerShell bug where single-item arrays are sometimes written as a single object instead of an array.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@createArray(activity('Get Config').output.firstRow.scopes)", - "type": "Expression" - } - } - }, - { - "name": "Filter Invalid Scopes", - "description": "Remove any invalid scopes to avoid errors.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Succeeded", - "Failed" - ] - }, - { - "activity": "Set Scopes as Array", - "dependencyConditions": [ - "Skipped", - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@variables('scopesArray')", - "type": "Expression" - }, - "condition": { - "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", - "type": "Expression" - } - } - }, - { - "name": "ForEach Export Scope", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Filter Invalid Scopes", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@activity('Filter Invalid Scopes').output.Value", - "type": "Expression" - }, - "isSequential": true, - "activities": [ - { - "name": "Set backfill export name", - "type": "SetVariable", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "variableName": "exportName", - "value": { - "value": "@toLower(concat(variables('finOpsHub'), '-monthly-costdetails'))", - "type": "Expression" - } - } - }, - { - "name": "Trigger backfill export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Set backfill export name", - "dependencyConditions": [ - "Completed" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 1, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}/run?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "POST", - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunBackfill@{0}', variables('finOpsToolkitVersion'))]", - "Content-Type": "application/json", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "body": "{\"timePeriod\" : { \"from\" : \"@{pipeline().parameters.StartDate}\", \"to\" : \"@{pipeline().parameters.EndDate}\" }}", - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - } - ] - } - } - ], - "concurrency": 1, - "parameters": { - "StartDate": { - "type": "string" - }, - "EndDate": { - "type": "string" - } - }, - "variables": { - "exportName": { - "type": "String" - }, - "storageAccountId": { - "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "finOpsHub": { - "type": "String", - "defaultValue": "[parameters('app').hub.name]" - }, - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[variables('CONFIG')]" - }, - "scopesArray": { - "type": "Array" - } - } - } - }, - "dataFactory::pipeline_StartExportProcess": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_StartExportProcess', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('fileName')", - "type": "Expression" - }, - "folderPath": { - "value": "@variables('folderPath')", - "type": "Expression" - } - } - } - } - }, - { - "name": "Set Scopes", - "description": "Save scopes to test if it is an array", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@activity('Get Config').output.firstRow.scopes", - "type": "Expression" - } - } - }, - { - "name": "Set Scopes as Array", - "description": "Wraps a single scope object into an array to work around the PowerShell bug where single-item arrays are sometimes written as a single object instead of an array.", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@createArray(activity('Get Config').output.firstRow.scopes)", - "type": "Expression" - } - } - }, - { - "name": "Filter Invalid Scopes", - "description": "Remove any invalid scopes to avoid errors.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Succeeded", - "Failed" - ] - }, - { - "activity": "Set Scopes as Array", - "dependencyConditions": [ - "Succeeded", - "Skipped" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@variables('scopesArray')", - "type": "Expression" - }, - "condition": { - "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", - "type": "Expression" - } - } - }, - { - "name": "ForEach Export Scope", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Filter Invalid Scopes", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@activity('Filter Invalid Scopes').output.Value", - "type": "Expression" - }, - "isSequential": true, - "activities": [ - { - "name": "Get exports for scope", - "type": "WebActivity", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "GET", - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Run exports for scope", - "type": "ExecutePipeline", - "dependsOn": [ - { - "activity": "Get exports for scope", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_RunExportJobs', variables('CONFIG'))]", - "type": "PipelineReference" - }, - "waitOnCompletion": true, - "parameters": { - "ExportScopes": { - "value": "@activity('Get exports for scope').output.value", - "type": "Expression" - }, - "Recurrence": { - "value": "@pipeline().parameters.Recurrence", - "type": "Expression" - } - } - } - } - ] - } - } - ], - "concurrency": 1, - "parameters": { - "Recurrence": { - "type": "string", - "defaultValue": "Daily" - } - }, - "variables": { - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[variables('CONFIG')]" - }, - "finOpsHub": { - "type": "String", - "defaultValue": "[parameters('app').hub.name]" - }, - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "scopesArray": { - "type": "Array" - } - } - }, - "dependsOn": [ - "dataFactory::pipeline_RunExportJobs" - ] - }, - "dataFactory::pipeline_RunExportJobs": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_RunExportJobs', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "ForEach export scope", - "type": "ForEach", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@pipeline().parameters.exportScopes", - "type": "Expression" - }, - "isSequential": true, - "activities": [ - { - "name": "If scheduled", - "type": "IfCondition", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@and( startswith(toLower(item().name), toLower(variables('hubName'))), and(contains(string(item().properties.schedule), 'recurrence'), equals(toLower(item().properties.schedule.recurrence), toLower(pipeline().parameters.Recurrence))))", - "type": "Expression" - }, - "ifTrueActivities": [ - { - "name": "Trigger export", - "type": "WebActivity", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "method": "POST", - "url": { - "value": "[format('@{{replace(toLower(concat(variables(''resourceManagementUri''),item().id)), ''com//'', ''com/'')}}/run?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "body": " ", - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - } - ] - } - } - ] - } - } - ], - "concurrency": 1, - "parameters": { - "ExportScopes": { - "type": "array" - }, - "Recurrence": { - "type": "string", - "defaultValue": "Daily" - } - }, - "variables": { - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "hubName": { - "type": "String", - "defaultValue": "[parameters('app').hub.name]" - } - } - }, - "dependsOn": [ - "dataFactory::dataset_config" - ] - }, - "dataFactory::pipeline_ConfigureExports": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ConfigureExports', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": { - "value": "@variables('fileName')", - "type": "Expression" - }, - "folderPath": { - "value": "@variables('folderPath')", - "type": "Expression" - } - } - } - } - }, - { - "name": "Save Scopes", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@activity('Get Config').output.firstRow.scopes", - "type": "Expression" - } - } - }, - { - "name": "Save Scopes as Array", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Save Scopes", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "scopesArray", - "value": { - "value": "@array(activity('Get Config').output.firstRow.scopes)", - "type": "Expression" - } - } - }, - { - "name": "Filter Invalid Scopes", - "type": "Filter", - "dependsOn": [ - { - "activity": "Save Scopes", - "dependencyConditions": [ - "Succeeded", - "Failed" - ] - }, - { - "activity": "Save Scopes as Array", - "dependencyConditions": [ - "Skipped", - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@variables('scopesArray')", - "type": "Expression" - }, - "condition": { - "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", - "type": "Expression" - } - } - }, - { - "name": "ForEach Export Scope", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Filter Invalid Scopes", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@activity('Filter Invalid Scopes').output.value", - "type": "Expression" - }, - "isSequential": true, - "activities": [ - { - "name": "Set Export Type", - "type": "SetVariable", - "dependsOn": [], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "exportScopeType", - "value": { - "value": "@if(contains(toLower(item().scope), 'providers/microsoft.billing/billingaccounts'), if(contains(toLower(item().scope), ':'), 'mca', 'ea'), if(contains(toLower(item().scope), 'subscriptions/'), 'subscription', 'undefined'))", - "type": "Expression" - } - } - }, - { - "name": "Switch Export Type", - "type": "Switch", - "dependsOn": [ - { - "activity": "Set Export Type", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "on": { - "value": "@toLower(variables('exportScopeType'))", - "type": "Expression" - }, - "cases": [ - { - "value": "ea", - "activities": [ - { - "name": "Open month focus export", - "type": "WebActivity", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Closed month focus export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Open month focus export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Monthly pricesheet export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Closed month focus export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'Pricesheet', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Trigger EA monthly pricesheet export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Monthly pricesheet export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "method": "POST", - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}/run?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "body": " ", - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Daily reservation details export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Monthly pricesheet export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationDetails', false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationDetails@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Daily reservation transactions export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Daily reservation details export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationtransactions''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationTransactions', false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationTransactions@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Daily shared 30day virtual machines", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Daily reservation transactions export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-recommendations-shared-last30days-virtualmachines''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'ReservationRecommendations', false(), 'CSV', 'None', 'true', 'CreateNewReport', 'Shared', 'Last30Days', 'VirtualMachines')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationRecommendations.VM.Shared.30d@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - } - ] - }, - { - "value": "subscription", - "activities": [ - { - "name": "Subscription open month focus export", - "type": "WebActivity", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - }, - { - "name": "Subscription closed month focus export", - "type": "WebActivity", - "dependsOn": [ - { - "activity": "Subscription open month focus export", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "url": { - "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportsApiVersion'))]", - "type": "Expression" - }, - "method": "PUT", - "body": { - "value": "[__bicep.getExportBodyV2(variables('MSEXPORTS'), 'FocusCost', true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", - "type": "Expression" - }, - "headers": { - "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('finOpsToolkitVersion'))]", - "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('finOpsToolkitVersion'))]" - }, - "authentication": { - "type": "MSI", - "resource": { - "value": "@variables('resourceManagementUri')", - "type": "Expression" - } - } - } - } - ] - }, - { - "value": "mca", - "activities": [ - { - "name": "Export Type Unsupported Error", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('MCA agreements are not supported for managed exports :',variables('exportScope'))", - "type": "Expression" - }, - "errorCode": "ExportTypeUnsupported" - } - } - ] - } - ], - "defaultActivities": [ - { - "name": "Export Type Not Defined Error", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to determine the export scope type for :',variables('exportScope'))", - "type": "Expression" - }, - "errorCode": "ExportTypeNotDefined" - } - } - ] - } - } - ] - } - } - ], - "concurrency": 1, - "variables": { - "scopesArray": { - "type": "Array" - }, - "exportName": { - "type": "String" - }, - "exportScope": { - "type": "String" - }, - "exportScopeType": { - "type": "String" - }, - "storageAccountId": { - "type": "String", - "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "finOpsHub": { - "type": "String", - "defaultValue": "[parameters('app').hub.name]" - }, - "resourceManagementUri": { - "type": "String", - "defaultValue": "[environment().resourceManager]" - }, - "fileName": { - "type": "String", - "defaultValue": "settings.json" - }, - "folderPath": { - "type": "String", - "defaultValue": "[variables('CONFIG')]" - } - } - } - }, - "storageAccount": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]" - }, - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]" - }, - "appRegistration": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.ManagedExports_Register", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "version": { - "value": "[variables('finOpsToolkitVersion')]" - }, - "features": { - "value": [ - "DataFactory" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5436870138046688593" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppFeature": { - "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], - "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "version": { - "type": "string", - "metadata": { - "description": "Required. Version number of the FinOps hub app." - } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." - } - }, - "storageRoles": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." - } - }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0}', parameters('app').id)]", - "version": "[parameters('version')]" - } - }, - "resources": [] - } - }, - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", - "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" - }, - "resources": { - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", - "properties": { - "name": "[parameters('app').storage]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "storageAccount" - ] - }, - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", - "properties": { - "name": "[parameters('app').keyVault]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "keyVault" - ] - }, - "dataFactory::managedVirtualNetwork": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "properties": {}, - "dependsOn": [ - "dataFactory" - ] - }, - "dataFactory::managedIntegrationRuntime": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", - "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "default", - "type": "ManagedVirtualNetworkReference" - }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('app').hub.location]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 - } - } - } - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedVirtualNetwork" - ] - }, - "dataFactory::linkedService_keyVault": { - "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "keyVault" - ] - }, - "dataFactory::linkedService_storageAccount": { - "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "storageAccount" - ] - }, - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] - }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] - }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] - }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] - }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] - }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" - } - } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] - }, - "appTelemetry": { - "condition": "[parameters('app').hub.options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", - "properties": "[variables('telemetryProps')]" - }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" - } - } - }, - "storageRoleAssignments": { - "copy": { - "name": "storageRoleAssignments", - "count": "[length(variables('factoryStorageRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "storageAccount" - ] - }, - "triggerManagerIdentity": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "dependsOn": [ - "dataFactory" - ] - }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "triggerManagerIdentity" - ] - }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "location": "[parameters('app').hub.location]", - "sku": { - "name": "[parameters('app').hub.options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" - }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "blob" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" - }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] - }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", - "properties": { - "sku": { - "name": "[parameters('app').hub.options.keyVaultSku]", - "family": "A" - }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" - } - }, - "dependsOn": [ - "dataFactory" - ] - }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} - }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('app').keyVault)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.keyVault]" - }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] - }, - "dependsOn": [ - "keyVault" - ] - }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", - "getStoragePrivateEndpointConnections", - "keyVault" - ] - }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "getKeyVaultPrivateEndpointConnections", - "keyVault" - ] - }, - "getStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", - "stopTriggers", - "storageAccount" - ] - }, - "approveStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "getStoragePrivateEndpointConnections", - "storageAccount" - ] - }, - "stopTriggers": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "arguments": { - "value": "-Stop" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('app').dataFactory]" - } - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } - } - } - }, - "dependsOn": [ - "appTelemetry", - "dataFactory", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" - ] - } - }, - "outputs": { - "dataFactoryId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Factory instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" - }, - "keyVaultId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Key Vault instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" - }, - "storageAccountId": { - "type": "string", - "metadata": { - "description": "Resource ID of the storage account instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - }, - "triggerManagerIdentityName": { - "type": "string", - "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." - }, - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - } - } - } - } - }, - "timeZones": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.CostManagement.ManagedExports_TimeZones", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "location": { - "value": "[parameters('app').hub.location]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "6509457716792571662" - } - }, - "parameters": { - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." - } - }, - "timezoneobject": { - "type": "object", - "defaultValue": { - "australiaeast": "AUS Eastern Standard Time", - "australiacentral": "AUS Eastern Standard Time", - "australiacentral2": "AUS Eastern Standard Time", - "australiasoutheast": "AUS Eastern Standard Time", - "brazilsouth": "E. South America Standard Time", - "canadacentral": "Central Standard Time", - "canadaeast": "Eastern Standard Time", - "centralindia": "India Standard Time", - "centralus": "Central Standard Time", - "eastasia": "China Standard Time", - "eastus": "Eastern Standard Time", - "eastus2": "Eastern Standard Time", - "francecentral": "W. Europe Standard Time", - "germanynorth": "W. Europe Standard Time", - "germanywestcentral": "W. Europe Standard Time", - "japaneast": "Japan Standard Time", - "japanwest": "Japan Standard Time", - "koreacentral": "Korea Standard Time", - "koreasouth": "Korea Standard Time", - "northcentralus": "Central Standard Time", - "northeurope": "GMT Standard Time", - "norwayeast": "W. Europe Standard Time", - "norwaywest": "W. Europe Standard Time", - "southcentralus": "Central Standard Time", - "southindia": "India Standard Time", - "southeastasia": "Singapore Standard Time", - "switzerlandnorth": "W. Europe Standard Time", - "switzerlandwest": "W. Europe Standard Time", - "uksouth": "GMT Standard Time", - "ukwest": "GMT Standard Time", - "westcentralus": "Central Standard Time", - "westeurope": "W. Europe Standard Time", - "westindia": "India Standard Time", - "westus": "Pacific Standard Time", - "westus2": "Pacific Standard Time" - } - }, - "utchrs": { - "type": "string", - "defaultValue": "[utcNow('hh')]" - }, - "utcmins": { - "type": "string", - "defaultValue": "[utcNow('mm')]" - }, - "utcsecs": { - "type": "string", - "defaultValue": "[utcNow('ss')]" - } - }, - "variables": { - "loc": "[toLower(replace(parameters('location'), ' ', ''))]", - "timezone": "[coalesce(tryGet(parameters('timezoneobject'), variables('loc')), 'Universal Coordinated Time')]" - }, - "resources": [], - "outputs": { - "AzureRegion": { - "type": "string", - "value": "[parameters('location')]" - }, - "Timezone": { - "type": "string", - "value": "[variables('timezone')]" - }, - "UtcHours": { - "type": "string", - "value": "[parameters('utchrs')]" - }, - "UtcMinutes": { - "type": "string", - "value": "[parameters('utcmins')]" - }, - "UtcSeconds": { - "type": "string", - "value": "[parameters('utcsecs')]" - } - } - } - } - }, - "trigger_SettingsUpdated": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core_SettingsUpdatedTrigger", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('app').dataFactory]" - }, - "triggerName": { - "value": "[format('{0}_SettingsUpdated', variables('CONFIG'))]" - }, - "pipelineName": { - "value": "[format('{0}_ConfigureExports', variables('CONFIG'))]" - }, - "pipelineParameters": { - "value": {} - }, - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "storageContainer": { - "value": "[variables('CONFIG')]" - }, - "storagePathEndsWith": { - "value": "settings.json" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "14264521107451792604" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } - }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." - } - }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." - } - }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." - } - }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." - } - }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." - } - }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." - } - } - }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" - }, - "parameters": "[parameters('pipelineParameters')]" - } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] - } - } - } - ] - } - }, - "dependsOn": [ - "dataFactory::pipeline_ConfigureExports" - ] - } - } - } - }, - "dependsOn": [ - "cmExports" - ] - }, - "analytics": { - "condition": "[or(variables('useFabric'), variables('useAzureDataExplorer'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'Analytics')]" - }, - "fabricQueryUri": { - "value": "[parameters('fabricQueryUri')]" - }, - "fabricCapacityUnits": { - "value": "[parameters('fabricCapacityUnits')]" - }, - "clusterName": { - "value": "[parameters('dataExplorerName')]" - }, - "clusterSku": { - "value": "[parameters('dataExplorerSku')]" - }, - "clusterCapacity": { - "value": "[parameters('dataExplorerCapacity')]" - }, - "rawRetentionInDays": { - "value": "[parameters('dataExplorerRawRetentionInDays')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "16399190021391778181" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "privateRoutingForLinkedServices": { - "parameters": [ - { - "$ref": "#/definitions/_1.HubProperties", - "name": "hub" - } - ], - "output": { - "type": "object", - "value": "[if(parameters('hub').options.privateRouting, createObject('connectVia', createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference')), createObject())]" - }, - "metadata": { - "description": "Returns an object that represents the properties needed to enable private routing for linked services. Use property expansion (`...value`) to apply to a linkedServices resource.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "clusterName": { - "type": "string", - "defaultValue": "", - "maxLength": 22, - "metadata": { - "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics. If empty, Azure Data Explorer will not be deployed. Required to use with Power BI if you have more than $2-5M/mo in costs being monitored. Default: \"\" (do not use)." - } - }, - "clusterSku": { - "type": "string", - "defaultValue": "Dev(No SLA)_Standard_E2a_v4", - "allowedValues": [ - "Dev(No SLA)_Standard_E2a_v4", - "Dev(No SLA)_Standard_D11_v2", - "Standard_D11_v2", - "Standard_D12_v2", - "Standard_D13_v2", - "Standard_D14_v2", - "Standard_D16d_v5", - "Standard_D32d_v4", - "Standard_D32d_v5", - "Standard_DS13_v2+1TB_PS", - "Standard_DS13_v2+2TB_PS", - "Standard_DS14_v2+3TB_PS", - "Standard_DS14_v2+4TB_PS", - "Standard_E2a_v4", - "Standard_E2ads_v5", - "Standard_E2d_v4", - "Standard_E2d_v5", - "Standard_E4a_v4", - "Standard_E4ads_v5", - "Standard_E4d_v4", - "Standard_E4d_v5", - "Standard_E8a_v4", - "Standard_E8ads_v5", - "Standard_E8as_v4+1TB_PS", - "Standard_E8as_v4+2TB_PS", - "Standard_E8as_v5+1TB_PS", - "Standard_E8as_v5+2TB_PS", - "Standard_E8d_v4", - "Standard_E8d_v5", - "Standard_E8s_v4+1TB_PS", - "Standard_E8s_v4+2TB_PS", - "Standard_E8s_v5+1TB_PS", - "Standard_E8s_v5+2TB_PS", - "Standard_E16a_v4", - "Standard_E16ads_v5", - "Standard_E16as_v4+3TB_PS", - "Standard_E16as_v4+4TB_PS", - "Standard_E16as_v5+3TB_PS", - "Standard_E16as_v5+4TB_PS", - "Standard_E16d_v4", - "Standard_E16d_v5", - "Standard_E16s_v4+3TB_PS", - "Standard_E16s_v4+4TB_PS", - "Standard_E16s_v5+3TB_PS", - "Standard_E16s_v5+4TB_PS", - "Standard_E64i_v3", - "Standard_E80ids_v4", - "Standard_EC8ads_v5", - "Standard_EC8as_v5+1TB_PS", - "Standard_EC8as_v5+2TB_PS", - "Standard_EC16ads_v5", - "Standard_EC16as_v5+3TB_PS", - "Standard_EC16as_v5+4TB_PS", - "Standard_L4s", - "Standard_L8as_v3", - "Standard_L8s", - "Standard_L8s_v2", - "Standard_L8s_v3", - "Standard_L16as_v3", - "Standard_L16s", - "Standard_L16s_v2", - "Standard_L16s_v3", - "Standard_L32as_v3", - "Standard_L32s_v3" - ], - "metadata": { - "description": "Optional. Name of the Azure Data Explorer SKU. Default: \"Dev(No SLA)_Standard_E2a_v4\"." - } - }, - "clusterCapacity": { - "type": "int", - "defaultValue": 1, - "minValue": 1, - "maxValue": 1000, - "metadata": { - "description": "Optional. Number of nodes to use in the cluster. Allowed values: 1 for the Basic SKU tier and 2-1000 for Standard. Default: 1 for dev/test SKUs, 2 for standard SKUs." - } - }, - "fabricQueryUri": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Microsoft Fabric eventhouse query URI. Default: \"\" (do not use)." - } - }, - "fabricCapacityUnits": { - "type": "int", - "defaultValue": 2, - "minValue": 1, - "maxValue": 2048, - "metadata": { - "description": "Optional. Number of capacity units for the Microsoft Fabric capacity. This is the number in your Fabric SKU (e.g., Trial = 1, F2 = 2, F64 = 64). This is used to manage parallelization in data pipelines. If you change capacity, please redeploy the template. Allowed values: 1 for the Fabric trial and 2-2048 based on the assigned Fabric capacity (e.g., F2-F2048). Default: 2." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." - } - }, - "rawRetentionInDays": { - "type": "int", - "metadata": { - "description": "Required. Number of days of data to retain in the Data Explorer *_raw tables." - } - } - }, - "variables": { - "$fxv#0": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_1(id: string) {\n dynamic({\n \"arizeai.observabilityeval/organizations\": { \"SingularDisplayName\": \"Azure Native Arize AI Cloud Service\" }\n ,\"astronomer.astro/organizations\": { \"SingularDisplayName\": \"Astro Organization\" }\n ,\"citrix.services/xenappessentials\": { \"SingularDisplayName\": \"Citrix Virtual Apps Essentials\" }\n ,\"citrix.services/xendesktopessentials\": { \"SingularDisplayName\": \"Citrix Virtual Desktops Essentials\" }\n ,\"commvault.contentstore/cloudaccounts\": { \"SingularDisplayName\": \"Commvault Cloud Account\" }\n ,\"commvault.contentstore/cloudaccounts/plans\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts plan\" }\n ,\"commvault.contentstore/cloudaccounts/protectiongroups\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection group\" }\n ,\"commvault.contentstore/cloudaccounts/protectiongroups/protecteditems\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection groups protected item\" }\n ,\"commvault.contentstore/cloudaccounts/storages\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts storage\" }\n ,\"dell.storage/filesystems\": { \"SingularDisplayName\": \"Dell PowerScale\" }\n ,\"dynatrace.observability/monitors\": { \"SingularDisplayName\": \"Dynatrace\" }\n ,\"github.network/networksettings\": { \"SingularDisplayName\": \"GitHub.Network network setting\" }\n ,\"informatica.datamanagement/organizations\": { \"SingularDisplayName\": \"Informatica Organization\" }\n ,\"lambdatest.hyperexecute/organizations\": { \"SingularDisplayName\": \"Azure Native LambdaTest - HyperExecute Cloud Service\" }\n ,\"microsoft.aad/domainservices\": { \"SingularDisplayName\": \"Microsoft Entra Domain Services\" }\n ,\"microsoft.aadiam/diagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.aadiam diagnostic setting\" }\n ,\"microsoft.aadiam/privatelinkforazuread\": { \"SingularDisplayName\": \"Private Link for Microsoft Entra ID\" }\n ,\"microsoft.advisor/advisorscore\": { \"SingularDisplayName\": \"Microsoft.Advisor advisor score\" }\n ,\"microsoft.advisor/assessments\": { \"SingularDisplayName\": \"Microsoft.Advisor assessment\" }\n ,\"microsoft.advisor/configurations\": { \"SingularDisplayName\": \"Microsoft.Advisor configuration\" }\n ,\"microsoft.advisor/generaterecommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor generate recommendation\" }\n ,\"microsoft.advisor/metadata\": { \"SingularDisplayName\": \"Microsoft.Advisor metadata\" }\n ,\"microsoft.advisor/recommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendation\" }\n ,\"microsoft.advisor/recommendations/suppressions\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendations suppression\" }\n ,\"microsoft.advisor/resiliencyreviews\": { \"SingularDisplayName\": \"Microsoft.Advisor resiliency review\" }\n ,\"microsoft.agfoodplatform/farmbeats\": { \"SingularDisplayName\": \"Azure Data Manager for Agriculture\" }\n ,\"microsoft.agfoodplatform/farmbeatsextensiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats extension definition\" }\n ,\"microsoft.agfoodplatform/farmbeatssolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats solution definition\" }\n ,\"microsoft.agricultureplatform/agriservices\": { \"SingularDisplayName\": \"Agriculture data solutions\" }\n ,\"microsoft.akshybrid/agentpools\": { \"SingularDisplayName\": \"Microsoft.AksHybrid agent pool\" }\n ,\"microsoft.akshybrid/provisionedclusters\": { \"SingularDisplayName\": \"Microsoft.AksHybrid provisioned cluster\" }\n ,\"microsoft.akshybrid/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.AksHybrid upgrade profile\" }\n ,\"microsoft.alertsmanagement/actionrules\": { \"SingularDisplayName\": \"Alert processing rule\" }\n ,\"microsoft.alertsmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alert\" }\n ,\"microsoft.alertsmanagement/alerts/enrichments\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alerts enrichment\" }\n ,\"microsoft.alertsmanagement/prometheusrulegroups\": { \"SingularDisplayName\": \"Prometheus rule group\" }\n ,\"microsoft.alertsmanagement/smartdetectoralertrules\": { \"SingularDisplayName\": \"Smart detector alert rule\" }\n ,\"microsoft.alertsmanagement/smartgroups\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement smart group\" }\n ,\"microsoft.alertsmanagement/tenantactivitylogalerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement tenant activity log alert\" }\n ,\"microsoft.all/arcvirtualmachines\": { \"SingularDisplayName\": \"Azure Arc virtual machine\" }\n ,\"microsoft.all/hcivirtualmachines\": { \"SingularDisplayName\": \"Azure Local Virtual Machine - Azure Arc\" }\n ,\"microsoft.all/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.analysisservices/servers\": { \"SingularDisplayName\": \"Analysis Services server\" }\n ,\"microsoft.anybuild/clusters\": { \"SingularDisplayName\": \"AnyBuild cluster\" }\n ,\"microsoft.apicenter/deletedservices\": { \"SingularDisplayName\": \"Microsoft.ApiCenter deleted service\" }\n ,\"microsoft.apicenter/services\": { \"SingularDisplayName\": \"API Center\" }\n ,\"microsoft.apicenter/services/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.apimanagement/gateways\": { \"SingularDisplayName\": \"API Management gateway\" }\n ,\"microsoft.apimanagement/gateways/configconnections\": { \"SingularDisplayName\": \"Microsoft.ApiManagement gateways config connection\" }\n ,\"microsoft.apimanagement/service\": { \"SingularDisplayName\": \"API Management service\" }\n ,\"microsoft.apimanagement/service/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.apisecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.ApiSecurity defender setting\" }\n ,\"microsoft.app/agents\": { \"SingularDisplayName\": \"SRE Agent\" }\n ,\"microsoft.app/builders\": { \"SingularDisplayName\": \"Microsoft.App builder\" }\n ,\"microsoft.app/builders/builds\": { \"SingularDisplayName\": \"Microsoft.App builders build\" }\n ,\"microsoft.app/connectedenvironments\": { \"SingularDisplayName\": \"Container Apps Connected Environment\" }\n ,\"microsoft.app/containerapps\": { \"SingularDisplayName\": \"Container App\" }\n ,\"microsoft.app/jobs\": { \"SingularDisplayName\": \"Container App Job\" }\n ,\"microsoft.app/logicapps\": { \"SingularDisplayName\": \"Logic app\" }\n ,\"microsoft.app/logicapps/workflows\": { \"SingularDisplayName\": \"Logic app workflow\" }\n ,\"microsoft.app/managedenvironments\": { \"SingularDisplayName\": \"Container Apps Environment\" }\n ,\"microsoft.app/sessionpools\": { \"SingularDisplayName\": \"Container App Session Pool\" }\n ,\"microsoft.app/spaces\": { \"SingularDisplayName\": \"App Space\" }\n ,\"microsoft.appassessment/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate project\" }\n ,\"microsoft.appassessment/migrateprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessment\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedapplications\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed application\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed machine\" }\n ,\"microsoft.appassessment/migrateprojects/assessments/machinestoassess\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments machines to asses\" }\n ,\"microsoft.appassessment/migrateprojects/sites\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects site\" }\n ,\"microsoft.appassessment/migrateprojects/sites/applianceconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects sites appliance configuration\" }\n ,\"microsoft.appcomplianceautomation/reports\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation report\" }\n ,\"microsoft.appcomplianceautomation/reports/evidences\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports evidence\" }\n ,\"microsoft.appcomplianceautomation/reports/scopingconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports scoping configuration\" }\n ,\"microsoft.appcomplianceautomation/reports/snapshots\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshot\" }\n ,\"microsoft.appcomplianceautomation/reports/snapshots/controls\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshots control\" }\n ,\"microsoft.appcomplianceautomation/reports/webhooks\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports webhook\" }\n ,\"microsoft.appconfiguration/configurationstores\": { \"SingularDisplayName\": \"App Configuration\" }\n ,\"microsoft.applicationmigration/discoveryhubs\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hub\" }\n ,\"microsoft.applicationmigration/discoveryhubs/applications\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs application\" }\n ,\"microsoft.applicationmigration/discoveryhubs/applications/members\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs applications member\" }\n ,\"microsoft.applicationmigration/pgsqlsites\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsite\" }\n ,\"microsoft.applicationmigration/pgsqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites agent\" }\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqldatabases\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqldatabase\" }\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqlinstances\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqlinstance\" }\n ,\"microsoft.appplatform/spring\": { \"SingularDisplayName\": \"Azure Spring Apps\" }\n ,\"microsoft.appsecurity/appprotectmanagedrulesetmanifests\": { \"SingularDisplayName\": \"Microsoft.AppSecurity app protect managed rule set manifest\" }\n ,\"microsoft.appsecurity/policies\": { \"SingularDisplayName\": \"App Protect Policy\" }\n ,\"microsoft.arc/all\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\n ,\"microsoft.arc/allfairfax\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\n ,\"microsoft.arc/kubernetesresources\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\n ,\"microsoft.arc/kubernetesresourcesfairfax\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\n ,\"microsoft.arcnetworking/arcnwloadbalancers\": { \"SingularDisplayName\": \"Microsoft.ArcNetworking arc nw load balancer\" }\n ,\"microsoft.aszlabhardware/labservers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware labserver\" }\n ,\"microsoft.aszlabhardware/reservations\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservation\" }\n ,\"microsoft.aszlabhardware/reservations/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservations server\" }\n ,\"microsoft.aszlabhardware/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware server\" }\n ,\"microsoft.attestation/attestationproviders\": { \"SingularDisplayName\": \"Attestation provider\" }\n ,\"microsoft.authorization/accessreviewhistorydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review history definition\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definition\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instance\" }\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances/decisions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instances decision\" }\n ,\"microsoft.authorization/accessreviewschedulesettings\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule setting\" }\n ,\"microsoft.authorization/datapolicymanifests\": { \"SingularDisplayName\": \"Microsoft.Authorization data policy manifest\" }\n ,\"microsoft.authorization/denyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization deny assignment\" }\n ,\"microsoft.authorization/locks\": { \"SingularDisplayName\": \"Microsoft.Authorization lock\" }\n ,\"microsoft.authorization/policyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization policy assignment\" }\n ,\"microsoft.authorization/policydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definition\" }\n ,\"microsoft.authorization/policydefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definitions version\" }\n ,\"microsoft.authorization/policyexemptions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy exemption\" }\n ,\"microsoft.authorization/policysetdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definition\" }\n ,\"microsoft.authorization/policysetdefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definitions version\" }\n ,\"microsoft.authorization/privatelinkassociations\": { \"SingularDisplayName\": \"Microsoft.Authorization private link association\" }\n ,\"microsoft.authorization/provideroperations\": { \"SingularDisplayName\": \"Microsoft.Authorization provider operation\" }\n ,\"microsoft.authorization/resourcemanagementprivatelinks\": { \"SingularDisplayName\": \"Resource management private link\" }\n ,\"microsoft.authorization/roleassignmentapprovals\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approval\" }\n ,\"microsoft.authorization/roleassignmentapprovals/stages\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approvals stage\" }\n ,\"microsoft.authorization/roleassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment\" }\n ,\"microsoft.authorization/roleassignmentscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule instance\" }\n ,\"microsoft.authorization/roleassignmentschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule request\" }\n ,\"microsoft.authorization/roleassignmentschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule\" }\n ,\"microsoft.authorization/roledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role definition\" }\n ,\"microsoft.authorization/roleeligibilityscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule instance\" }\n ,\"microsoft.authorization/roleeligibilityschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule request\" }\n ,\"microsoft.authorization/roleeligibilityschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule\" }\n ,\"microsoft.authorization/rolemanagementalertconfigurations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert configuration\" }\n ,\"microsoft.authorization/rolemanagementalertdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert definition\" }\n ,\"microsoft.authorization/rolemanagementalertoperations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert operation\" }\n ,\"microsoft.authorization/rolemanagementalerts\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert\" }\n ,\"microsoft.authorization/rolemanagementalerts/alertincidents\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alerts alert incident\" }\n ,\"microsoft.authorization/rolemanagementpolicies\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy\" }\n ,\"microsoft.authorization/rolemanagementpolicyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy assignment\" }\n ,\"microsoft.automanage/bestpractices\": { \"SingularDisplayName\": \"Microsoft.Automanage best practice\" }\n ,\"microsoft.automanage/bestpractices/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage best practices version\" }\n ,\"microsoft.automanage/configurationprofileassignments\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignment\" }\n ,\"microsoft.automanage/configurationprofileassignments/reports\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignments report\" }\n ,\"microsoft.automanage/configurationprofiles\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile\" }\n ,\"microsoft.automanage/configurationprofiles/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profiles version\" }\n ,\"microsoft.automanage/serviceprincipals\": { \"SingularDisplayName\": \"ServicePrincipals\" }\n ,\"microsoft.automation/automationaccounts\": { \"SingularDisplayName\": \"Automation account\" }\n ,\"microsoft.automation/automationaccounts/hybridrunbookworkergroups\": { \"SingularDisplayName\": \"Automation hybrid worker group\" }\n ,\"microsoft.automation/automationaccounts/runbooks\": { \"SingularDisplayName\": \"Automation runbook\" }\n ,\"microsoft.autonomousdevelopmentplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform account\" }\n ,\"microsoft.autonomousdevelopmentplatform/accounts/datapools\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform accounts data pool\" }\n ,\"microsoft.autonomousdevelopmentplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform workspace\" }\n ,\"microsoft.avs/privateclouds\": { \"SingularDisplayName\": \"Azure VMware Solution private cloud\" }\n ,\"microsoft.awsconnector/accessanalyzeranalyzers\": { \"SingularDisplayName\": \"Access Analyzer Analyzer\" }\n ,\"microsoft.awsconnector/acmcertificatesummaries\": { \"SingularDisplayName\": \"ACM Certificate Summary\" }\n ,\"microsoft.awsconnector/apigatewayrestapis\": { \"SingularDisplayName\": \"Api Gateway Rest Api\" }\n ,\"microsoft.awsconnector/apigatewaystages\": { \"SingularDisplayName\": \"Api Gateway Stage\" }\n ,\"microsoft.awsconnector/applicationautoscalingscalabletargets\": { \"SingularDisplayName\": \"Application Auto Scaling Scalable Target\" }\n ,\"microsoft.awsconnector/appsyncgraphqlapis\": { \"SingularDisplayName\": \"App Sync Graphql Api\" }\n ,\"microsoft.awsconnector/autoscalingautoscalinggroups\": { \"SingularDisplayName\": \"Auto Scaling Auto Scaling Group\" }\n ,\"microsoft.awsconnector/cloudformationstacks\": { \"SingularDisplayName\": \"Cloud Formation Stack\" }\n ,\"microsoft.awsconnector/cloudformationstacksets\": { \"SingularDisplayName\": \"Cloud Formation Stack Set\" }\n ,\"microsoft.awsconnector/cloudfrontdistributions\": { \"SingularDisplayName\": \"Cloud Front Distribution\" }\n ,\"microsoft.awsconnector/cloudtrailtrails\": { \"SingularDisplayName\": \"Cloud Trail Trail\" }\n ,\"microsoft.awsconnector/cloudwatchalarms\": { \"SingularDisplayName\": \"Cloud Watch Alarm\" }\n ,\"microsoft.awsconnector/codebuildprojects\": { \"SingularDisplayName\": \"Code Build Project\" }\n ,\"microsoft.awsconnector/codebuildsourcecredentialsinfos\": { \"SingularDisplayName\": \"Code Build Source Credentials Info\" }\n ,\"microsoft.awsconnector/configserviceconfigurationrecorders\": { \"SingularDisplayName\": \"Config Service Configuration Recorder\" }\n ,\"microsoft.awsconnector/configserviceconfigurationrecorderstatuses\": { \"SingularDisplayName\": \"Config Service Configuration Recorder Status\" }\n ,\"microsoft.awsconnector/configservicedeliverychannels\": { \"SingularDisplayName\": \"Config Service Delivery Channel\" }\n ,\"microsoft.awsconnector/databasemigrationservicereplicationinstances\": { \"SingularDisplayName\": \"Database Migration Service Replication Instance\" }\n ,\"microsoft.awsconnector/daxclusters\": { \"SingularDisplayName\": \"DAX Cluster\" }\n ,\"microsoft.awsconnector/dynamodbcontinuousbackupsdescriptions\": { \"SingularDisplayName\": \"Dynamo DB Continuous Backups Description\" }\n ,\"microsoft.awsconnector/dynamodbtables\": { \"SingularDisplayName\": \"Dynamo DB Table\" }\n ,\"microsoft.awsconnector/ec2accountattributes\": { \"SingularDisplayName\": \"EC2 Account Attribute\" }\n ,\"microsoft.awsconnector/ec2addresses\": { \"SingularDisplayName\": \"EC2 Address\" }\n ,\"microsoft.awsconnector/ec2flowlogs\": { \"SingularDisplayName\": \"EC2 Flow Log\" }\n ,\"microsoft.awsconnector/ec2images\": { \"SingularDisplayName\": \"EC2 Image\" }\n ,\"microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\n ,\"microsoft.awsconnector/ec2instancestatuses\": { \"SingularDisplayName\": \"EC2 Instance Status\" }\n ,\"microsoft.awsconnector/ec2ipams\": { \"SingularDisplayName\": \"EC2 Ipam\" }\n ,\"microsoft.awsconnector/ec2keypairs\": { \"SingularDisplayName\": \"EC2 Key Pair\" }\n ,\"microsoft.awsconnector/ec2networkacls\": { \"SingularDisplayName\": \"EC2 Network Acl\" }\n ,\"microsoft.awsconnector/ec2networkinterfaces\": { \"SingularDisplayName\": \"EC2 Network Interface\" }\n ,\"microsoft.awsconnector/ec2routetables\": { \"SingularDisplayName\": \"EC2 Route Table\" }\n ,\"microsoft.awsconnector/ec2securitygroups\": { \"SingularDisplayName\": \"EC2 Security Group\" }\n ,\"microsoft.awsconnector/ec2snapshots\": { \"SingularDisplayName\": \"EC2 Snapshot\" }\n ,\"microsoft.awsconnector/ec2subnets\": { \"SingularDisplayName\": \"EC2 Subnet\" }\n ,\"microsoft.awsconnector/ec2volumes\": { \"SingularDisplayName\": \"EC2 Volume\" }\n ,\"microsoft.awsconnector/ec2vpcendpoints\": { \"SingularDisplayName\": \"EC2 VPCEndpoint\" }\n ,\"microsoft.awsconnector/ec2vpcpeeringconnections\": { \"SingularDisplayName\": \"EC2 VPCPeering Connection\" }\n ,\"microsoft.awsconnector/ec2vpcs\": { \"SingularDisplayName\": \"EC2 VPC\" }\n ,\"microsoft.awsconnector/ecrimagedetails\": { \"SingularDisplayName\": \"ECR Image Detail\" }\n ,\"microsoft.awsconnector/ecrrepositories\": { \"SingularDisplayName\": \"ECR Repository\" }\n ,\"microsoft.awsconnector/ecsclusters\": { \"SingularDisplayName\": \"ECS Cluster\" }\n ,\"microsoft.awsconnector/ecsservices\": { \"SingularDisplayName\": \"ECS Service\" }\n ,\"microsoft.awsconnector/ecstaskdefinitions\": { \"SingularDisplayName\": \"ECS Task Definition\" }\n ,\"microsoft.awsconnector/efsfilesystems\": { \"SingularDisplayName\": \"EFS File System\" }\n ,\"microsoft.awsconnector/efsmounttargets\": { \"SingularDisplayName\": \"EFS Mount Target\" }\n ,\"microsoft.awsconnector/eksnodegroups\": { \"SingularDisplayName\": \"EKS Nodegroup\" }\n ,\"microsoft.awsconnector/elasticbeanstalkapplications\": { \"SingularDisplayName\": \"Elastic Beanstalk Application\" }\n ,\"microsoft.awsconnector/elasticbeanstalkconfigurationtemplates\": { \"SingularDisplayName\": \"Elastic Beanstalk Configuration Template\" }\n ,\"microsoft.awsconnector/elasticbeanstalkenvironments\": { \"SingularDisplayName\": \"Elastic Beanstalk Environment\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2listeners\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Listener\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2loadbalancers\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Load Balancer\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2targetgroups\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Target Group\" }\n ,\"microsoft.awsconnector/elasticloadbalancingv2targethealthdescriptions\": { \"SingularDisplayName\": \"Elastic Load Balancing v2 Target Health Description\" }\n ,\"microsoft.awsconnector/elasticsearchdomains\": { \"SingularDisplayName\": \"Elasticsearch Domain\" }\n ,\"microsoft.awsconnector/emrclusters\": { \"SingularDisplayName\": \"EMR Cluster\" }\n ,\"microsoft.awsconnector/guarddutydetectors\": { \"SingularDisplayName\": \"Guard Duty Detector\" }\n ,\"microsoft.awsconnector/iamaccesskeylastuseds\": { \"SingularDisplayName\": \"IAM Access Key Last Used\" }\n ,\"microsoft.awsconnector/iamaccesskeymetadata\": { \"SingularDisplayName\": \"IAM Access Key Metadata\" }\n ,\"microsoft.awsconnector/iamgroups\": { \"SingularDisplayName\": \"IAM Group\" }\n ,\"microsoft.awsconnector/iaminstanceprofiles\": { \"SingularDisplayName\": \"IAM Instance Profile\" }\n ,\"microsoft.awsconnector/iammanagedpolicies\": { \"SingularDisplayName\": \"IAM Managed Policy\" }\n ,\"microsoft.awsconnector/iammfadevices\": { \"SingularDisplayName\": \"IAM MFADevice\" }\n ,\"microsoft.awsconnector/iampasswordpolicies\": { \"SingularDisplayName\": \"IAM Password Policy\" }\n ,\"microsoft.awsconnector/iampolicyversions\": { \"SingularDisplayName\": \"IAM Policy Version\" }\n ,\"microsoft.awsconnector/iamroles\": { \"SingularDisplayName\": \"IAM Role\" }\n ,\"microsoft.awsconnector/iamservercertificates\": { \"SingularDisplayName\": \"IAM Server Certificate\" }\n ,\"microsoft.awsconnector/iamuserpolicies\": { \"SingularDisplayName\": \"IAM User Policy\" }\n ,\"microsoft.awsconnector/iamvirtualmfadevices\": { \"SingularDisplayName\": \"IAM Virtual MFADevice\" }\n ,\"microsoft.awsconnector/kmsaliases\": { \"SingularDisplayName\": \"KMS Alias\" }\n ,\"microsoft.awsconnector/kmskeys\": { \"SingularDisplayName\": \"KMS Key\" }\n ,\"microsoft.awsconnector/lambdafunctioncodelocations\": { \"SingularDisplayName\": \"Lambda Function Code Location\" }\n ,\"microsoft.awsconnector/lambdafunctionconfigurations\": { \"SingularDisplayName\": \"Microsoft.AwsConnector lambda function configuration\" }\n ,\"microsoft.awsconnector/lambdafunctions\": { \"SingularDisplayName\": \"Lambda Function\" }\n ,\"microsoft.awsconnector/licensemanagerlicenses\": { \"SingularDisplayName\": \"License Manager License\" }\n ,\"microsoft.awsconnector/lightsailbuckets\": { \"SingularDisplayName\": \"Lightsail Bucket\" }\n ,\"microsoft.awsconnector/lightsailinstances\": { \"SingularDisplayName\": \"Lightsail Instance\" }\n ,\"microsoft.awsconnector/logsloggroups\": { \"SingularDisplayName\": \"Logs Log Group\" }\n ,\"microsoft.awsconnector/logslogstreams\": { \"SingularDisplayName\": \"Logs Log Stream\" }\n ,\"microsoft.awsconnector/logsmetricfilters\": { \"SingularDisplayName\": \"Logs Metric Filter\" }\n ,\"microsoft.awsconnector/logssubscriptionfilters\": { \"SingularDisplayName\": \"Logs Subscription Filter\" }\n ,\"microsoft.awsconnector/macie2jobsummaries\": { \"SingularDisplayName\": \"Macie2 Job Summary\" }\n ,\"microsoft.awsconnector/macieallowlists\": { \"SingularDisplayName\": \"Macie Allow List\" }\n ,\"microsoft.awsconnector/networkfirewallfirewallpolicies\": { \"SingularDisplayName\": \"Network Firewall Firewall Policy\" }\n ,\"microsoft.awsconnector/networkfirewallfirewalls\": { \"SingularDisplayName\": \"Network Firewall Firewall\" }\n ,\"microsoft.awsconnector/networkfirewallrulegroups\": { \"SingularDisplayName\": \"Network Firewall Rule Group\" }\n ,\"microsoft.awsconnector/opensearchdomainstatuses\": { \"SingularDisplayName\": \"Open Search Domain Status\" }\n ,\"microsoft.awsconnector/opensearchservicedomains\": { \"SingularDisplayName\": \"Open Search Service Domain\" }\n ,\"microsoft.awsconnector/organizationsaccounts\": { \"SingularDisplayName\": \"Organizations Account\" }\n ,\"microsoft.awsconnector/organizationsorganizations\": { \"SingularDisplayName\": \"Organizations Organization\" }\n ,\"microsoft.awsconnector/rdsdbclusters\": { \"SingularDisplayName\": \"RDS DBCluster\" }\n ,\"microsoft.awsconnector/rdsdbinstances\": { \"SingularDisplayName\": \"RDS DBInstance\" }\n ,\"microsoft.awsconnector/rdsdbsnapshotattributesresults\": { \"SingularDisplayName\": \"RDS DBSnapshot Attributes Result\" }\n ,\"microsoft.awsconnector/rdsdbsnapshots\": { \"SingularDisplayName\": \"RDS DBSnapshot\" }\n ,\"microsoft.awsconnector/rdseventsubscriptions\": { \"SingularDisplayName\": \"RDS Event Subscription\" }\n ,\"microsoft.awsconnector/rdsexporttasks\": { \"SingularDisplayName\": \"RDS Export Task\" }\n ,\"microsoft.awsconnector/redshiftclusterparametergroups\": { \"SingularDisplayName\": \"Redshift Cluster Parameter Group\" }\n ,\"microsoft.awsconnector/redshiftclusters\": { \"SingularDisplayName\": \"Redshift Cluster\" }\n ,\"microsoft.awsconnector/route53domainsdomainsummaries\": { \"SingularDisplayName\": \"Route 53 Domains Domain Summary\" }\n ,\"microsoft.awsconnector/route53hostedzones\": { \"SingularDisplayName\": \"Route53 Hosted Zone\" }\n ,\"microsoft.awsconnector/route53resourcerecordsets\": { \"SingularDisplayName\": \"Route 53 Resource Record Set\" }\n ,\"microsoft.awsconnector/s3accesscontrolpolicies\": { \"SingularDisplayName\": \"S3 Access Control Policy\" }\n ,\"microsoft.awsconnector/s3accesspoints\": { \"SingularDisplayName\": \"S3 Access Point\" }\n ,\"microsoft.awsconnector/s3bucketpolicies\": { \"SingularDisplayName\": \"S3 Bucket Policy\" }\n ,\"microsoft.awsconnector/s3buckets\": { \"SingularDisplayName\": \"S3 Bucket\" }\n ,\"microsoft.awsconnector/s3controlmultiregionaccesspointpolicydocuments\": { \"SingularDisplayName\": \"S3 Control Multi Region Access Point Policy Document\" }\n ,\"microsoft.awsconnector/sagemakerapps\": { \"SingularDisplayName\": \"Sage Maker App\" }\n ,\"microsoft.awsconnector/sagemakerdevices\": { \"SingularDisplayName\": \"Sage Maker Device\" }\n ,\"microsoft.awsconnector/sagemakerimages\": { \"SingularDisplayName\": \"Sage Maker Image\" }\n ,\"microsoft.awsconnector/sagemakernotebookinstancesummaries\": { \"SingularDisplayName\": \"Sage Maker Notebook Instance Summary\" }\n ,\"microsoft.awsconnector/secretsmanagerresourcepolicies\": { \"SingularDisplayName\": \"Secrets Manager Resource Policy\" }\n ,\"microsoft.awsconnector/secretsmanagersecrets\": { \"SingularDisplayName\": \"Secrets Manager Secret\" }\n ,\"microsoft.awsconnector/snssubscriptions\": { \"SingularDisplayName\": \"SNS Subscription\" }\n ,\"microsoft.awsconnector/snstopics\": { \"SingularDisplayName\": \"SNS Topic\" }\n ,\"microsoft.awsconnector/sqsqueues\": { \"SingularDisplayName\": \"SQS Queue\" }\n ,\"microsoft.awsconnector/ssminstanceinformations\": { \"SingularDisplayName\": \"SSM Instance Information\" }\n ,\"microsoft.awsconnector/ssmparameters\": { \"SingularDisplayName\": \"SSM Parameter\" }\n ,\"microsoft.awsconnector/ssmresourcecompliancesummaryitems\": { \"SingularDisplayName\": \"SSM Resource Compliance Summary Item\" }\n ,\"microsoft.awsconnector/wafv2ipsets\": { \"SingularDisplayName\": \"WAFv2 IPSet\" }\n ,\"microsoft.awsconnector/wafv2loggingconfigurations\": { \"SingularDisplayName\": \"WAFv2 Logging Configuration\" }\n ,\"microsoft.awsconnector/wafv2webaclassociations\": { \"SingularDisplayName\": \"WAFv2 Web ACLAssociation\" }\n ,\"microsoft.awsconnector/wafwebaclsummaries\": { \"SingularDisplayName\": \"WAF Web ACLSummary\" }\n ,\"microsoft.azureactivedirectory/b2cdirectories\": { \"SingularDisplayName\": \"B2C tenant\" }\n ,\"microsoft.azureactivedirectory/ciamdirectories\": { \"SingularDisplayName\": \"External Configuration Tenant\" }\n ,\"microsoft.azureactivedirectory/guestusages\": { \"SingularDisplayName\": \"Guest Usage\" }\n ,\"microsoft.azurearcdata/datacontrollers\": { \"SingularDisplayName\": \"Azure Arc data controller\" }\n ,\"microsoft.azurearcdata/mysqlserver\": { \"SingularDisplayName\": \"MySql Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/postgresinstances\": { \"SingularDisplayName\": \"PostgreSQL server ? Azure Arc\" }\n ,\"microsoft.azurearcdata/postgressqlserver\": { \"SingularDisplayName\": \"PostgresSql Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlmanagedinstances\": { \"SingularDisplayName\": \"SQL managed instance - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserveresulicenses\": { \"SingularDisplayName\": \"SQL Server ESU license\" }\n ,\"microsoft.azurearcdata/sqlserverinstances\": { \"SingularDisplayName\": \"SQL Server - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserverinstances/databases\": { \"SingularDisplayName\": \"SQL Server database - Azure Arc\" }\n ,\"microsoft.azurearcdata/sqlserverlicenses\": { \"SingularDisplayName\": \"SQL Server License\" }\n ,\"microsoft.azurebusinesscontinuity/deletedunifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity deleted unified protected item\" }\n ,\"microsoft.azurebusinesscontinuity/unifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity unified protected item\" }\n ,\"microsoft.azurecis/aadapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis AAD application\" }\n ,\"microsoft.azurecis/addressrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis address record\" }\n ,\"microsoft.azurecis/autopilotenvironments\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot environment\" }\n ,\"microsoft.azurecis/autopilotmachinefunctions\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot machine function\" }\n ,\"microsoft.azurecis/autopilotsoftwareloadbalancevirtualips\": { \"SingularDisplayName\": \"Microsoft.AzureCis auto pilot software load balance virtual IP\" }\n ,\"microsoft.azurecis/azcopies\": { \"SingularDisplayName\": \"Microsoft.AzureCis az copy\" }\n ,\"microsoft.azurecis/canonicalnamerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis canonical name record\" }\n ,\"microsoft.azurecis/dsmsallowlists\": { \"SingularDisplayName\": \"Microsoft.AzureCis ds msallowlist\" }\n ,\"microsoft.azurecis/dsmscertificates\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms certificate\" }\n ,\"microsoft.azurecis/dsmsrootfolders\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms root folder\" }\n ,\"microsoft.azurecis/dstsapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts application\" }\n ,\"microsoft.azurecis/dstsserviceaccounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service account\" }\n ,\"microsoft.azurecis/dstsserviceclientidentities\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service client identity\" }\n ,\"microsoft.azurecis/genericgenevaactions\": { \"SingularDisplayName\": \"Microsoft.AzureCis generic geneva action\" }\n ,\"microsoft.azurecis/plannedquotas\": { \"SingularDisplayName\": \"Microsoft.AzureCis planned quota\" }\n ,\"microsoft.azurecis/pointerrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis pointer record\" }\n ,\"microsoft.azurecis/publishconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis publish config value\" }\n ,\"microsoft.azurecis/pushagentv2accounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis push agent v2 account\" }\n ,\"microsoft.azurecis/servicerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis service record\" }\n ,\"microsoft.azurecis/sharedconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis shared config value\" }\n ,\"microsoft.azurecloudmetadata/clouds\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata cloud\" }\n ,\"microsoft.azurecloudmetadata/clouds/geographies\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geography\" }\n ,\"microsoft.azurecloudmetadata/clouds/geographies/regions\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geographies region\" }\n ,\"microsoft.azuredatatransfer/connections\": { \"SingularDisplayName\": \"Connection\" }\n ,\"microsoft.azuredatatransfer/connections/flows\": { \"SingularDisplayName\": \"Flow\" }\n ,\"microsoft.azuredatatransfer/pipelines\": { \"SingularDisplayName\": \"Pipeline\" }\n ,\"microsoft.azurefleet/fleets\": { \"SingularDisplayName\": \"Compute Fleet\" }\n ,\"microsoft.azurefleet/fleetscomputehub\": { \"SingularDisplayName\": \"Compute Fleet\" }\n ,\"microsoft.azureimagetestingforlinux/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job\" }\n ,\"microsoft.azureimagetestingforlinux/jobtemplates\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job template\" }\n ,\"microsoft.azurelargeinstance/azurelargeinstances\": { \"SingularDisplayName\": \"Azure Large Instance\" }\n ,\"microsoft.azurelargeinstance/azurelargestorageinstances\": { \"SingularDisplayName\": \"Microsoft.AzureLargeInstance Azure large storage instance\" }\n ,\"microsoft.azurepercept/accounts\": { \"SingularDisplayName\": \"Microsoft.AzurePercept account\" }\n ,\"microsoft.azurepercept/accounts/devices\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts device\" }\n ,\"microsoft.azurepercept/accounts/devices/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts devices sensor\" }\n ,\"microsoft.azurepercept/accounts/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts sensor\" }\n ,\"microsoft.azurepercept/accounts/solutioninstances\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solutioninstance\" }\n ,\"microsoft.azurepercept/accounts/solutions\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solution\" }\n ,\"microsoft.azurepercept/accounts/targets\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts target\" }\n ,\"microsoft.azureplaywrightservice/accounts\": { \"SingularDisplayName\": \"Playwright Testing\" }\n ,\"microsoft.azurescan/scanningaccounts\": { \"SingularDisplayName\": \"ESRP Scan\" }\n ,\"microsoft.azuresphere/catalogs\": { \"SingularDisplayName\": \"Azure Sphere Catalog\" }\n ,\"microsoft.azurespherev2/catalogs\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalog\" }\n ,\"microsoft.azurespherev2/catalogs/artifacts\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs artifact\" }\n ,\"microsoft.azurespherev2/catalogs/certificates\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs certificate\" }\n ,\"microsoft.azurespherev2/catalogs/deviceregistrations\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs device registration\" }\n ,\"microsoft.azurespherev2/catalogs/provisioningpackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs provisioning package\" }\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channel\" }\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels/deployments\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channels deployment\" }\n ,\"microsoft.azurespherev2/catalogs/updatepackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs update package\" }\n ,\"microsoft.azurestack/cloudmanifestfiles\": { \"SingularDisplayName\": \"Microsoft.AzureStack cloud manifest file\" }\n ,\"microsoft.azurestack/linkedsubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack linked subscription\" }\n ,\"microsoft.azurestack/registrations\": { \"SingularDisplayName\": \"Microsoft.AzureStack registration\" }\n ,\"microsoft.azurestack/registrations/customersubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations customer subscription\" }\n ,\"microsoft.azurestack/registrations/products\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations product\" }\n ,\"microsoft.azurestackhci/clusters\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/clusters/updates/updateruns\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/clusters/updatesummaries\": { \"SingularDisplayName\": \"Azure Local\" }\n ,\"microsoft.azurestackhci/devicepools\": { \"SingularDisplayName\": \"Azure Stack\" }\n ,\"microsoft.azurestackhci/edgedevices\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge device\" }\n ,\"microsoft.azurestackhci/edgedevices/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge devices job\" }\n ,\"microsoft.azurestackhci/edgemachines\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machine\" }\n ,\"microsoft.azurestackhci/edgemachines/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machines job\" }\n ,\"microsoft.azurestackhci/edgenodepools\": { \"SingularDisplayName\": \"Azure Stack\" }\n ,\"microsoft.azurestackhci/galleryimages\": { \"SingularDisplayName\": \"Azure Local Gallery image\" }\n ,\"microsoft.azurestackhci/logicalnetworks\": { \"SingularDisplayName\": \"Azure Local Logical network\" }\n ,\"microsoft.azurestackhci/marketplacegalleryimages\": { \"SingularDisplayName\": \"Azure Local Marketplace Gallery image\" }\n ,\"microsoft.azurestackhci/networkinterfaces\": { \"SingularDisplayName\": \"Azure Local VM Network Interface\" }\n ,\"microsoft.azurestackhci/networksecuritygroups\": { \"SingularDisplayName\": \"Azure Local Network Security Group\" }\n ,\"microsoft.azurestackhci/networksecuritygroups/securityrules\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI network security groups security rule\" }\n ,\"microsoft.azurestackhci/storagecontainers\": { \"SingularDisplayName\": \"Azure Local Storage path\" }\n ,\"microsoft.azurestackhci/virtualharddisks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual hard disk\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instance\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances guest agent\" }\n ,\"microsoft.azurestackhci/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.azurestackhci/virtualmachines\": { \"SingularDisplayName\": \"Azure Local virtual machine - Azure Arc\" }\n ,\"microsoft.azurestackhci/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual network\" }\n ,\"microsoft.backupsolutions/vmwareapplications\": { \"SingularDisplayName\": \"Microsoft.BackupSolutions vmware application\" }\n ,\"microsoft.bakeryhybrid/pies\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid py\" }\n ,\"microsoft.bakeryhybrid/pies/nestedresourcetype\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid pies nested resource type\" }\n ,\"microsoft.baremetal/baremetalconnections\": { \"SingularDisplayName\": \"Microsoft.BareMetal bare metal connection\" }\n ,\"microsoft.baremetal/crayservers\": { \"SingularDisplayName\": \"Cray Server\" }\n ,\"microsoft.baremetal/monitoringservers\": { \"SingularDisplayName\": \"Monitoring Server\" }\n ,\"microsoft.baremetal/peeringsettings\": { \"SingularDisplayName\": \"Microsoft.BareMetal peering setting\" }\n ,\"microsoft.baremetalinfrastructure/baremetalinstances\": { \"SingularDisplayName\": \"BareMetal Instance\" }\n ,\"microsoft.baremetalinfrastructure/baremetalstorageinstances\": { \"SingularDisplayName\": \"Microsoft.BareMetalInfrastructure bare metal storage instance\" }\n ,\"microsoft.batch/batchaccounts\": { \"SingularDisplayName\": \"Batch account\" }\n ,\"microsoft.billing/billingaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing account\" }\n ,\"microsoft.billing/billingaccounts/agreements\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts agreement\" }\n ,\"microsoft.billing/billingaccounts/associatedtenants\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts associated tenant\" }\n ,\"microsoft.billing/billingaccounts/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts available balance\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profile\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles available balance\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers transfer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/instructions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles instruction\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice section\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections product\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections transfer\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/paymentmethodlinks\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles payment method link\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles policy\" }\n ,\"microsoft.billing/billingaccounts/billingprofiles/transactions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles transaction\" }\n ,\"microsoft.billing/billingaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role definition\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptionaliases\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription aliase\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription\" }\n ,\"microsoft.billing/billingaccounts/billingsubscriptions/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscriptions invoice\" }\n ,\"microsoft.billing/billingaccounts/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customer\" }\n ,\"microsoft.billing/billingaccounts/customers/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers billing subscription\" }\n ,\"microsoft.billing/billingaccounts/customers/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers policy\" }\n ,\"microsoft.billing/billingaccounts/customers/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers product\" }\n ,\"microsoft.billing/billingaccounts/departments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts department\" }\n ,\"microsoft.billing/billingaccounts/departments/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/departments/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role definition\" }\n ,\"microsoft.billing/billingaccounts/departments/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments enrollment account\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment account\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role assignment\" }\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role definition\" }\n ,\"microsoft.billing/billingaccounts/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\n ,\"microsoft.billing/billingaccounts/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\n ,\"microsoft.billing/billingaccounts/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice\" }\n ,\"microsoft.billing/billingaccounts/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice section\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections billing subscription\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections product\" }\n ,\"microsoft.billing/billingaccounts/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections transfer\" }\n ,\"microsoft.billing/billingaccounts/lineofcredit\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts line of credit\" }\n ,\"microsoft.billing/billingaccounts/migrations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts migration\" }\n ,\"microsoft.billing/billingaccounts/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts payment method\" }\n ,\"microsoft.billing/billingaccounts/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts policy\" }\n ,\"microsoft.billing/billingaccounts/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts product\" }\n ,\"microsoft.billing/billingaccounts/reservationorders\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation order\" }\n ,\"microsoft.billing/billingaccounts/reservationorders/reservations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation orders reservation\" }\n ,\"microsoft.billing/billingaccounts/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\n ,\"microsoft.billing/billingaccounts/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\n ,\"microsoft.billing/billingperiods\": { \"SingularDisplayName\": \"Microsoft.Billing billing period\" }\n ,\"microsoft.billing/billingproperty\": { \"SingularDisplayName\": \"Microsoft.Billing billing property\" }\n ,\"microsoft.billing/billingrequests\": { \"SingularDisplayName\": \"Microsoft.Billing billing request\" }\n ,\"microsoft.billing/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing role assignment\" }\n ,\"microsoft.billing/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing role definition\" }\n ,\"microsoft.billing/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing enrollment account\" }\n ,\"microsoft.billing/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing payment method\" }\n ,\"microsoft.billing/policies\": { \"SingularDisplayName\": \"Microsoft.Billing policy\" }\n ,\"microsoft.billing/promotions\": { \"SingularDisplayName\": \"Microsoft.Billing promotion\" }\n ,\"microsoft.billing/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing transfer\" }\n ,\"microsoft.billingbenefits/credits\": { \"SingularDisplayName\": \"Credit\" }\n ,\"microsoft.billingbenefits/discounts\": { \"SingularDisplayName\": \"Discount\" }\n ,\"microsoft.billingbenefits/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\n ,\"microsoft.billingbenefits/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\n ,\"microsoft.billingbenefits/maccs\": { \"SingularDisplayName\": \"Microsoft Azure Consumption Commitment\" }\n ,\"microsoft.billingbenefits/reservationorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits reservation order aliase\" }\n ,\"microsoft.billingbenefits/savingsplanorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits savings plan order aliase\" }\n ,\"microsoft.billingbenefits/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\n ,\"microsoft.billingbenefits/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\n ,\"microsoft.bing/accounts\": { \"SingularDisplayName\": \"Bing Resource\" }\n ,\"microsoft.blockchain/blockchainmembers\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain member\" }\n ,\"microsoft.blockchain/blockchainmembers/transactionnodes\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain members transaction node\" }\n ,\"microsoft.blockchaintokens/tokenservices\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token service\" }\n ,\"microsoft.blockchaintokens/tokenservices/blockchainnetworks\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services blockchain network\" }\n ,\"microsoft.blockchaintokens/tokenservices/groups\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services group\" }\n ,\"microsoft.blockchaintokens/tokenservices/groups/accounts\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services groups account\" }\n ,\"microsoft.blockchaintokens/tokenservices/tokentemplates\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services token template\" }\n ,\"microsoft.bluefin/instances\": { \"SingularDisplayName\": \"Microsoft.Bluefin instance\" }\n ,\"microsoft.bluefin/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances dataset\" }\n ,\"microsoft.bluefin/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances pipeline\" }\n ,\"microsoft.blueprint/blueprintassignments\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint assignment\" }\n ,\"microsoft.blueprint/blueprints\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint\" }\n ,\"microsoft.blueprint/blueprints/artifacts\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints artifact\" }\n ,\"microsoft.blueprint/blueprints/versions\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints version\" }\n ,\"microsoft.botservice/botservices\": { \"SingularDisplayName\": \"Bot Service\" }\n ,\"microsoft.cache/redis\": { \"SingularDisplayName\": \"Redis cache\" }\n ,\"microsoft.cache/redisenterprise\": { \"SingularDisplayName\": \"Azure Managed Redis\" }\n ,\"microsoft.cache/redisenterprise/databases\": { \"SingularDisplayName\": \"Redis Enterprise database\" }\n ,\"microsoft.capacity/reservationorders\": { \"SingularDisplayName\": \"Reservation order\" }\n ,\"microsoft.capacity/reservationorders/reservations\": { \"SingularDisplayName\": \"Reservation\" }\n ,\"microsoft.cascade/sites\": { \"SingularDisplayName\": \"Microsoft.Cascade site\" }\n ,\"microsoft.cdn/cdnwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Content Delivery Network WAF policy\" }\n ,\"microsoft.cdn/edgeactions\": { \"SingularDisplayName\": \"Edge Action\" }\n ,\"microsoft.cdn/profiles\": { \"SingularDisplayName\": \"Front Door and CDN profile\" }\n ,\"microsoft.cdn/profiles/afdendpoints\": { \"SingularDisplayName\": \"Endpoint\" }\n ,\"microsoft.cdn/profiles/afdendpoints/routes\": { \"SingularDisplayName\": \"Route\" }\n ,\"microsoft.cdn/profiles/customdomains\": { \"SingularDisplayName\": \"Custom domain\" }\n ,\"microsoft.cdn/profiles/endpoints\": { \"SingularDisplayName\": \"CDN endpoint\" }\n ,\"microsoft.cdn/profiles/endpoints/customdomains\": { \"SingularDisplayName\": \"CDN custom domain\" }\n ,\"microsoft.cdn/profiles/endpoints/origins\": { \"SingularDisplayName\": \"CDN origin\" }\n ,\"microsoft.cdn/profiles/origingroups\": { \"SingularDisplayName\": \"Origin group\" }\n ,\"microsoft.cdn/profiles/origingroups/origins\": { \"SingularDisplayName\": \"Origin\" }\n ,\"microsoft.cdn/profiles/rulesets\": { \"SingularDisplayName\": \"Rule set\" }\n ,\"microsoft.cdn/profiles/rulesets/rules\": { \"SingularDisplayName\": \"Rule\" }\n ,\"microsoft.cdn/profiles/secrets\": { \"SingularDisplayName\": \"Secret\" }\n ,\"microsoft.cdn/profiles/securitypolicies\": { \"SingularDisplayName\": \"Security policy\" }\n ,\"microsoft.certificateregistration/certificateorders\": { \"SingularDisplayName\": \"App Service certificate\" }\n ,\"microsoft.certify/testsuites\": { \"SingularDisplayName\": \"Microsoft.Certify test suite\" }\n ,\"microsoft.certify/validationjobs\": { \"SingularDisplayName\": \"Microsoft.Certify validation job\" }\n ,\"microsoft.changeanalysis/profile\": { \"SingularDisplayName\": \"Microsoft.ChangeAnalysis profile\" }\n ,\"microsoft.changesafety/changestates\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change state\" }\n ,\"microsoft.changesafety/changestates/stageprogressions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change states stage progression\" }\n ,\"microsoft.changesafety/stagemaps\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety stage map\" }\n ,\"microsoft.changesafety/validations\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validation\" }\n ,\"microsoft.changesafety/validators\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validator\" }\n ,\"microsoft.changesafety/validators/versions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validators version\" }\n ,\"microsoft.chaos/experiments\": { \"SingularDisplayName\": \"Chaos Experiment\" }\n ,\"microsoft.chaos/privateaccesses\": { \"SingularDisplayName\": \"Agent Private Access\" }\n ,\"microsoft.chaos/targets\": { \"SingularDisplayName\": \"Microsoft.Chaos target\" }\n ,\"microsoft.chaos/targets/capabilities\": { \"SingularDisplayName\": \"Microsoft.Chaos targets capability\" }\n ,\"microsoft.classiccompute/domainnames\": { \"SingularDisplayName\": \"Cloud service (classic)\" }\n ,\"microsoft.classiccompute/domainnames/slots/roles\": { \"SingularDisplayName\": \"Cloud service role (classic)\" }\n ,\"microsoft.classiccompute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine (classic)\" }\n ,\"microsoft.classicnetwork/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group (classic)\" }\n ,\"microsoft.classicnetwork/reservedips\": { \"SingularDisplayName\": \"Reserved IP address (classic)\" }\n ,\"microsoft.classicnetwork/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network (classic)\" }\n })[tolower(id)]\n}\n", - "$fxv#1": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_2(id: string) {\n dynamic({\n \"microsoft.classicstorage/storageaccounts\": { \"SingularDisplayName\": \"Storage account (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/disks\": { \"SingularDisplayName\": \"Disk (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/osimages\": { \"SingularDisplayName\": \"OS image (classic)\" }\n ,\"microsoft.classicstorage/storageaccounts/vmimages\": { \"SingularDisplayName\": \"VM image (classic)\" }\n ,\"microsoft.cleanroom/cleanrooms\": { \"SingularDisplayName\": \"Microsoft.CleanRoom cleanroom\" }\n ,\"microsoft.cleanroom/collaborations\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaboration\" }\n ,\"microsoft.cleanroom/collaborations/contracts\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaborations contract\" }\n ,\"microsoft.cleanroom/consortiums\": { \"SingularDisplayName\": \"Microsoft.CleanRoom consortium\" }\n ,\"microsoft.cleanroom/microservices\": { \"SingularDisplayName\": \"Microsoft.CleanRoom microservice\" }\n ,\"microsoft.cloud/hubs\": { \"SingularDisplayName\": \"FinOps hub\" }\n ,\"microsoft.clouddeviceplatform/delegatedidentities\": { \"SingularDisplayName\": \"Microsoft.CloudDevicePlatform delegated identity\" }\n ,\"microsoft.cloudhealth/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\n ,\"microsoft.cloudtest/accounts\": { \"SingularDisplayName\": \"CloudTest Account\" }\n ,\"microsoft.cloudtest/buildcaches\": { \"SingularDisplayName\": \"1ES Build Cache\" }\n ,\"microsoft.cloudtest/hostedpools\": { \"SingularDisplayName\": \"1ES Hosted Pool\" }\n ,\"microsoft.cloudtest/images\": { \"SingularDisplayName\": \"1ES Image\" }\n ,\"microsoft.cloudtest/pools\": { \"SingularDisplayName\": \"CloudTest Pool\" }\n ,\"microsoft.clusterstor/nodes\": { \"SingularDisplayName\": \"ClusterStor\" }\n ,\"microsoft.codesigning/codesigningaccounts\": { \"SingularDisplayName\": \"Trusted Signing Account\" }\n ,\"microsoft.codespaces/plans\": { \"SingularDisplayName\": \"Microsoft.Codespaces plan\" }\n ,\"microsoft.cognitiveservices/accounts\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.cognitiveservices/accounts/projects\": { \"SingularDisplayName\": \"Azure AI Foundry project\" }\n ,\"microsoft.cognitiveservices/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plan\" }\n ,\"microsoft.cognitiveservices/commitmentplans/accountassociations\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plans account association\" }\n ,\"microsoft.communication/communicationservices\": { \"SingularDisplayName\": \"Communication Service\" }\n ,\"microsoft.communication/emailservices\": { \"SingularDisplayName\": \"Email Communication Service\" }\n ,\"microsoft.communication/emailservices/domains\": { \"SingularDisplayName\": \"Email Communication Services Domain\" }\n ,\"microsoft.community/communitytrainings\": { \"SingularDisplayName\": \"Community Training\" }\n ,\"microsoft.compositesolutions/compositesolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution definition\" }\n ,\"microsoft.compositesolutions/compositesolutions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution\" }\n ,\"microsoft.compute/availabilitysets\": { \"SingularDisplayName\": \"Availability set\" }\n ,\"microsoft.compute/capacityreservationgroups\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\n ,\"microsoft.compute/capacityreservationgroups/capacityreservations\": { \"SingularDisplayName\": \"Capacity reservation\" }\n ,\"microsoft.compute/capacityreservationgroupscomputehub\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\n ,\"microsoft.compute/cloudservices\": { \"SingularDisplayName\": \"Cloud service (extended support)\" }\n ,\"microsoft.compute/computefleetinstances\": { \"SingularDisplayName\": \"Instance\" }\n ,\"microsoft.compute/computefleetscalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.compute/diskaccesses\": { \"SingularDisplayName\": \"Disk Access\" }\n ,\"microsoft.compute/diskencryptionsets\": { \"SingularDisplayName\": \"Disk Encryption Set\" }\n ,\"microsoft.compute/disks\": { \"SingularDisplayName\": \"Disk\" }\n ,\"microsoft.compute/galleries\": { \"SingularDisplayName\": \"Azure compute gallery\" }\n ,\"microsoft.compute/galleries/applications\": { \"SingularDisplayName\": \"VM application definition\" }\n ,\"microsoft.compute/galleries/applications/versions\": { \"SingularDisplayName\": \"VM application version\" }\n ,\"microsoft.compute/galleries/images\": { \"SingularDisplayName\": \"VM image definition\" }\n ,\"microsoft.compute/galleries/images/versions\": { \"SingularDisplayName\": \"VM image version\" }\n ,\"microsoft.compute/galleries/imagescomputehub\": { \"SingularDisplayName\": \"VM image definition\" }\n ,\"microsoft.compute/hostgroups\": { \"SingularDisplayName\": \"Host group\" }\n ,\"microsoft.compute/hostgroups/hosts\": { \"SingularDisplayName\": \"Host\" }\n ,\"microsoft.compute/hostgroupscomputehub\": { \"SingularDisplayName\": \"Host group\" }\n ,\"microsoft.compute/images\": { \"SingularDisplayName\": \"Image\" }\n ,\"microsoft.compute/imagescomputehub\": { \"SingularDisplayName\": \"Image\" }\n ,\"microsoft.compute/locations/communitygalleries/images\": { \"SingularDisplayName\": \"Community image\" }\n ,\"microsoft.compute/locations/communitygalleries/imagescomputehub\": { \"SingularDisplayName\": \"Community image\" }\n ,\"microsoft.compute/proximityplacementgroups\": { \"SingularDisplayName\": \"Proximity placement group\" }\n ,\"microsoft.compute/proximityplacementgroupscomputehub\": { \"SingularDisplayName\": \"Proximity placement group\" }\n ,\"microsoft.compute/restorepointcollections\": { \"SingularDisplayName\": \"Restore Point Collection\" }\n ,\"microsoft.compute/restorepointcollections/restorepoints\": { \"SingularDisplayName\": \"Restore Point\" }\n ,\"microsoft.compute/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\n ,\"microsoft.compute/sshpublickeys\": { \"SingularDisplayName\": \"SSH key\" }\n ,\"microsoft.compute/standbypoolinstance\": { \"SingularDisplayName\": \"Standby pool\" }\n ,\"microsoft.compute/virtualmachinecomputehub\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.compute/virtualmachineflexinstances\": { \"SingularDisplayName\": \"Instance\" }\n ,\"microsoft.compute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\n ,\"microsoft.compute/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.compute/virtualmachinescalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.compute/virtualmachinescalesets/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine scale set instance\" }\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines/networkinterfaces/ipconfigurations/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\n ,\"microsoft.compute/virtualmachinescalesetscomputehub\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\n ,\"microsoft.computehub/advisorcost\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisoroperationalexcellence\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorperformance\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorreliability\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/advisorsecurity\": { \"SingularDisplayName\": \"Recommendations\" }\n ,\"microsoft.computehub/all\": { \"SingularDisplayName\": \"All resources\" }\n ,\"microsoft.computehub/backup\": { \"SingularDisplayName\": \"Backup job\" }\n ,\"microsoft.computehub/computehubmain\": { \"SingularDisplayName\": \"Compute infrastructure\" }\n ,\"microsoft.computehub/healthevents\": { \"SingularDisplayName\": \"Health events\" }\n ,\"microsoft.computehub/linuxostype\": { \"SingularDisplayName\": \"Linux OS\" }\n ,\"microsoft.computehub/microsoftdefenderfreetrialsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\n ,\"microsoft.computehub/microsoftdefenderstandardsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\n ,\"microsoft.computehub/outages\": { \"SingularDisplayName\": \"Outages\" }\n ,\"microsoft.computehub/powerstatedeallocated\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/powerstaterunning\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/powerstatestopped\": { \"SingularDisplayName\": \"Power states\" }\n ,\"microsoft.computehub/provisioningstatefailedresources\": { \"SingularDisplayName\": \"Provisioning states\" }\n ,\"microsoft.computehub/provisioningstatesucceededresources\": { \"SingularDisplayName\": \"Provisioning states\" }\n ,\"microsoft.computehub/windowsostype\": { \"SingularDisplayName\": \"Windows OS\" }\n ,\"microsoft.computeschedule/autoactions\": { \"SingularDisplayName\": \"Automatic Action\" }\n ,\"microsoft.computeschedule/autoactions/occurrences\": { \"SingularDisplayName\": \"Microsoft.ComputeSchedule auto actions occurrence\" }\n ,\"microsoft.confidentialledger/ledgers\": { \"SingularDisplayName\": \"Confidential Ledger\" }\n ,\"microsoft.confidentialledger/managedccfs\": { \"SingularDisplayName\": \"Managed CCF App\" }\n ,\"microsoft.confluent/agreements\": { \"SingularDisplayName\": \"Microsoft.Confluent agreement\" }\n ,\"microsoft.confluent/organizations\": { \"SingularDisplayName\": \"Confluent organization\" }\n ,\"microsoft.connectedcache/cachenodes\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\n ,\"microsoft.connectedcache/enterprisecustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\n ,\"microsoft.connectedcache/enterprisemcccustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\n ,\"microsoft.connectedcache/enterprisemcccustomers/enterprisemcccachenodes\": { \"SingularDisplayName\": \"MCC CacheNode for Enterprise\" }\n ,\"microsoft.connectedcache/ispcustomers\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\n ,\"microsoft.connectedcredentials/credentials\": { \"SingularDisplayName\": \"Microsoft.ConnectedCredentials credential\" }\n ,\"microsoft.connectedvehicle/platformaccounts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVehicle platform account\" }\n ,\"microsoft.connectedvmwarevsphere/clusters\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere cluster\" }\n ,\"microsoft.connectedvmwarevsphere/datastores\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere datastore\" }\n ,\"microsoft.connectedvmwarevsphere/hosts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere host\" }\n ,\"microsoft.connectedvmwarevsphere/resourcepools\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere resource pool\" }\n ,\"microsoft.connectedvmwarevsphere/vcenters\": { \"SingularDisplayName\": \"VMware vCenter\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instance\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances guest agent\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachines\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.connectedvmwarevsphere/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine template\" }\n ,\"microsoft.connectedvmwarevsphere/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual network\" }\n ,\"microsoft.consumption/budgets\": { \"SingularDisplayName\": \"Microsoft.Consumption budget\" }\n ,\"microsoft.consumption/credits\": { \"SingularDisplayName\": \"Microsoft.Consumption credit\" }\n ,\"microsoft.consumption/pricesheets\": { \"SingularDisplayName\": \"Microsoft.Consumption pricesheet\" }\n ,\"microsoft.containerinstance/containergroupprofiles\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profile\" }\n ,\"microsoft.containerinstance/containergroupprofiles/revisions\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profiles revision\" }\n ,\"microsoft.containerinstance/containergroups\": { \"SingularDisplayName\": \"Container instances\" }\n ,\"microsoft.containerinstance/ngroups\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance ngroup\" }\n ,\"microsoft.containerregistry/registries\": { \"SingularDisplayName\": \"Container registry\" }\n ,\"microsoft.containerregistry/registries/replications\": { \"SingularDisplayName\": \"Container registry replication\" }\n ,\"microsoft.containerregistry/registries/scopemaps\": { \"SingularDisplayName\": \"Container registry scope map\" }\n ,\"microsoft.containerregistry/registries/tokens\": { \"SingularDisplayName\": \"Container registry token\" }\n ,\"microsoft.containerregistry/registries/webhooks\": { \"SingularDisplayName\": \"Container registry webhook\" }\n ,\"microsoft.containerservice/fleets\": { \"SingularDisplayName\": \"Kubernetes fleet manager\" }\n ,\"microsoft.containerservice/managedclusters\": { \"SingularDisplayName\": \"Kubernetes service\" }\n ,\"microsoft.containerservice/managedclusters/managednamespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes namespace\" }\n ,\"microsoft.containerservice/managedclusters/namespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\n ,\"microsoft.containerservice/managedclustersnapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService managedclustersnapshot\" }\n ,\"microsoft.containerservice/snapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService snapshot\" }\n ,\"microsoft.containerstorage/pools\": { \"SingularDisplayName\": \"Container storage\" }\n ,\"microsoft.costmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.CostManagement alert\" }\n ,\"microsoft.costmanagement/budgets\": { \"SingularDisplayName\": \"Microsoft.CostManagement budget\" }\n ,\"microsoft.costmanagement/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement cloud connector\" }\n ,\"microsoft.costmanagement/connectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement connector\" }\n ,\"microsoft.costmanagement/costallocationrules\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost allocation rule\" }\n ,\"microsoft.costmanagement/costdetailsoperationresults\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost details operation result\" }\n ,\"microsoft.costmanagement/exports\": { \"SingularDisplayName\": \"Microsoft.CostManagement export\" }\n ,\"microsoft.costmanagement/externalbillingaccounts\": { \"SingularDisplayName\": \"Microsoft.CostManagement external billing account\" }\n ,\"microsoft.costmanagement/externalsubscriptions\": { \"SingularDisplayName\": \"Microsoft.CostManagement external subscription\" }\n ,\"microsoft.costmanagement/markuprules\": { \"SingularDisplayName\": \"Microsoft.CostManagement markup rule\" }\n ,\"microsoft.costmanagement/operationstatus\": { \"SingularDisplayName\": \"Microsoft.CostManagement operation statu\" }\n ,\"microsoft.costmanagement/reportconfigs\": { \"SingularDisplayName\": \"Microsoft.CostManagement reportconfig\" }\n ,\"microsoft.costmanagement/reports\": { \"SingularDisplayName\": \"Microsoft.CostManagement report\" }\n ,\"microsoft.costmanagement/scheduledactions\": { \"SingularDisplayName\": \"Microsoft.CostManagement scheduled action\" }\n ,\"microsoft.costmanagement/settings\": { \"SingularDisplayName\": \"Microsoft.CostManagement setting\" }\n ,\"microsoft.costmanagement/views\": { \"SingularDisplayName\": \"Microsoft.CostManagement view\" }\n ,\"microsoft.customerlockbox/requests\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox request\" }\n ,\"microsoft.customerlockbox/tenantoptedin\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox tenant opted in\" }\n ,\"microsoft.customproviders/associations\": { \"SingularDisplayName\": \"Microsoft.CustomProviders association\" }\n ,\"microsoft.customproviders/resourceproviders\": { \"SingularDisplayName\": \"Microsoft.CustomProviders resource provider\" }\n ,\"microsoft.dashboard/dashboards\": { \"SingularDisplayName\": \"Azure Monitor dashboards with Grafana\" }\n ,\"microsoft.dashboard/grafana\": { \"SingularDisplayName\": \"Azure Managed Grafana\" }\n ,\"microsoft.dataaccelerator/indexclusters\": { \"SingularDisplayName\": \"Microsoft.DataAccelerator index cluster\" }\n ,\"microsoft.databasefleetmanager/fleets\": { \"SingularDisplayName\": \"Database fleet manager\" }\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces\": { \"SingularDisplayName\": \"Fleetspaces\" }\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces/databases\": { \"SingularDisplayName\": \"Fleet managed database\" }\n ,\"microsoft.databasefleetmanager/fleets/tiers\": { \"SingularDisplayName\": \"tier\" }\n ,\"microsoft.databasewatcher/watchers\": { \"SingularDisplayName\": \"Database watcher\" }\n ,\"microsoft.databox/jobs\": { \"SingularDisplayName\": \"Azure Data Box\" }\n ,\"microsoft.databoxedge/databoxedgedevices\": { \"SingularDisplayName\": \"Azure Stack Edge / Data Box Gateway\" }\n ,\"microsoft.databricks/accessconnectors\": { \"SingularDisplayName\": \"Access Connector for Azure Databricks\" }\n ,\"microsoft.databricks/workspaces\": { \"SingularDisplayName\": \"Azure Databricks Service\" }\n ,\"microsoft.datacatalog/catalogs\": { \"SingularDisplayName\": \"Data catalog\" }\n ,\"microsoft.datacollaboration/workspaces\": { \"SingularDisplayName\": \"Project CI\" }\n ,\"microsoft.datadog/agreements\": { \"SingularDisplayName\": \"Microsoft.Datadog agreement\" }\n ,\"microsoft.datadog/monitors\": { \"SingularDisplayName\": \"Datadog\" }\n ,\"microsoft.datadog/subscriptionstatuses\": { \"SingularDisplayName\": \"Microsoft.Datadog subscription statuse\" }\n ,\"microsoft.datafactory/datafactories\": { \"SingularDisplayName\": \"Data factory\" }\n ,\"microsoft.datafactory/factories\": { \"SingularDisplayName\": \"Data factory (V2)\" }\n ,\"microsoft.datafactory/factories/pipelines\": { \"SingularDisplayName\": \"Data Factory pipeline\" }\n ,\"microsoft.datafactory/factories/triggers\": { \"SingularDisplayName\": \"Data Factory trigger\" }\n ,\"microsoft.datalakeanalytics/accounts\": { \"SingularDisplayName\": \"Data Lake Analytics account\" }\n ,\"microsoft.datalakestore/accounts\": { \"SingularDisplayName\": \"Data Lake Storage Gen1\" }\n ,\"microsoft.datamigration/databasemigrations\": { \"SingularDisplayName\": \"Microsoft.DataMigration database migration\" }\n ,\"microsoft.datamigration/migrationservices\": { \"SingularDisplayName\": \"Microsoft.DataMigration migration service\" }\n ,\"microsoft.datamigration/services\": { \"SingularDisplayName\": \"Azure Database Migration Service (classic)\" }\n ,\"microsoft.datamigration/services/projects\": { \"SingularDisplayName\": \"Azure Database Migration Project\" }\n ,\"microsoft.datamigration/sqlmigrationservices\": { \"SingularDisplayName\": \"Azure Database Migration Service\" }\n ,\"microsoft.dataprotection/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\n ,\"microsoft.dataprotection/resourceguards\": { \"SingularDisplayName\": \"Resource Guard\" }\n ,\"microsoft.datareplication/replicationfabrics\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabric\" }\n ,\"microsoft.datareplication/replicationfabrics/fabricagents\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agent\" }\n ,\"microsoft.datareplication/replicationfabrics/fabricagents/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agents operation\" }\n ,\"microsoft.datareplication/replicationfabrics/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics operation\" }\n ,\"microsoft.datareplication/replicationvaults\": { \"SingularDisplayName\": \"Data replication vault\" }\n ,\"microsoft.datareplication/replicationvaults/alertsettings\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults alert setting\" }\n ,\"microsoft.datareplication/replicationvaults/events\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults event\" }\n ,\"microsoft.datareplication/replicationvaults/jobs\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults job\" }\n ,\"microsoft.datareplication/replicationvaults/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults jobs operation\" }\n ,\"microsoft.datareplication/replicationvaults/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults operation\" }\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnectionproxies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection proxy\" }\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection\" }\n ,\"microsoft.datareplication/replicationvaults/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private link resource\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected item\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items operation\" }\n ,\"microsoft.datareplication/replicationvaults/protecteditems/recoverypoints\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items recovery point\" }\n ,\"microsoft.datareplication/replicationvaults/replicationextensions\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extension\" }\n ,\"microsoft.datareplication/replicationvaults/replicationextensions/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extensions operation\" }\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policy\" }\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policies operation\" }\n ,\"microsoft.datashare/accounts\": { \"SingularDisplayName\": \"Data Share\" }\n ,\"microsoft.dbformariadb/servers\": { \"SingularDisplayName\": \"Azure Database for MariaDB server\" }\n ,\"microsoft.dbformysql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for MySQL flexible server\" }\n ,\"microsoft.dbformysql/servers\": { \"SingularDisplayName\": \"MySQL server\" }\n ,\"microsoft.dbforpostgresql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for PostgreSQL flexible server\" }\n ,\"microsoft.dbforpostgresql/servergroupsv2\": { \"SingularDisplayName\": \"Azure Cosmos DB for PostgreSQL Cluster\" }\n ,\"microsoft.dbforpostgresql/servers\": { \"SingularDisplayName\": \"PostgreSQL server\" }\n ,\"microsoft.delegatednetwork/controller\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork controller\" }\n ,\"microsoft.delegatednetwork/delegatedsubnets\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork delegated subnet\" }\n ,\"microsoft.delegatednetwork/orchestrators\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork orchestrator\" }\n ,\"microsoft.dependencymap/maps\": { \"SingularDisplayName\": \"Microsoft.DependencyMap map\" }\n ,\"microsoft.dependencymap/maps/discoverysources\": { \"SingularDisplayName\": \"Microsoft.DependencyMap maps discovery source\" }\n ,\"microsoft.deploymentmanager/artifactsources\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager artifact source\" }\n ,\"microsoft.deploymentmanager/rollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.deploymentmanager/servicetopologies\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topology\" }\n ,\"microsoft.deploymentmanager/servicetopologies/services\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies service\" }\n ,\"microsoft.deploymentmanager/servicetopologies/services/serviceunits\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies services service unit\" }\n ,\"microsoft.deploymentmanager/steps\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager step\" }\n ,\"microsoft.desktopvirtualization/appattachpackages\": { \"SingularDisplayName\": \"App attach package\" }\n ,\"microsoft.desktopvirtualization/applicationgroups\": { \"SingularDisplayName\": \"Application group\" }\n ,\"microsoft.desktopvirtualization/hostpools\": { \"SingularDisplayName\": \"Host pool\" }\n ,\"microsoft.desktopvirtualization/scalingplans\": { \"SingularDisplayName\": \"Scaling plan\" }\n ,\"microsoft.desktopvirtualization/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\n ,\"microsoft.devai/instances\": { \"SingularDisplayName\": \"Microsoft.DevAI instance\" }\n ,\"microsoft.devai/instances/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances experiment\" }\n ,\"microsoft.devai/instances/sandboxes\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandbox\" }\n ,\"microsoft.devai/instances/sandboxes/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandboxes experiment\" }\n ,\"microsoft.devcenter/devcenters\": { \"SingularDisplayName\": \"Dev center\" }\n ,\"microsoft.devcenter/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Dev Box definition\" }\n ,\"microsoft.devcenter/networkconnections\": { \"SingularDisplayName\": \"Network connection\" }\n ,\"microsoft.devcenter/plans\": { \"SingularDisplayName\": \"Dev center plan\" }\n ,\"microsoft.devcenter/projects\": { \"SingularDisplayName\": \"Project\" }\n ,\"microsoft.devcenter/projects/pools\": { \"SingularDisplayName\": \"Pool\" }\n ,\"microsoft.developmentwindows365/developmentcloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.DevelopmentWindows365 development cloud pc delegated msi\" }\n ,\"microsoft.devhub/iacprofiles\": { \"SingularDisplayName\": \"Infrastructure as Code Automation\" }\n ,\"microsoft.devhub/templates\": { \"SingularDisplayName\": \"Microsoft.DevHub template\" }\n ,\"microsoft.devhub/templates/versions\": { \"SingularDisplayName\": \"Microsoft.DevHub templates version\" }\n ,\"microsoft.devhub/workflows\": { \"SingularDisplayName\": \"Microsoft.DevHub workflow\" }\n ,\"microsoft.deviceonboarding/discoveryservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery service\" }\n ,\"microsoft.deviceonboarding/discoveryservices/ownershipvoucherpublickeys\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery services ownership voucher public key\" }\n ,\"microsoft.deviceonboarding/onboardingservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding service\" }\n ,\"microsoft.deviceonboarding/onboardingservices/policies\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding services policy\" }\n ,\"microsoft.deviceregistry/assetendpointprofiles\": { \"SingularDisplayName\": \"IoT Asset Endpoint Profile\" }\n ,\"microsoft.deviceregistry/assets\": { \"SingularDisplayName\": \"IoT Asset\" }\n ,\"microsoft.deviceregistry/billingcontainers\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry billing container\" }\n ,\"microsoft.deviceregistry/devices\": { \"SingularDisplayName\": \"IoT Device\" }\n ,\"microsoft.deviceregistry/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset endpoint profile\" }\n ,\"microsoft.deviceregistry/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset\" }\n ,\"microsoft.deviceregistry/namespaces\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespace\" }\n ,\"microsoft.deviceregistry/namespaces/assetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset endpoint profile\" }\n ,\"microsoft.deviceregistry/namespaces/assets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset\" }\n ,\"microsoft.deviceregistry/namespaces/devices\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces device\" }\n ,\"microsoft.deviceregistry/namespaces/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset endpoint profile\" }\n ,\"microsoft.deviceregistry/namespaces/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset\" }\n ,\"microsoft.deviceregistry/schemaregistries\": { \"SingularDisplayName\": \"IoT Schema Registry\" }\n ,\"microsoft.deviceregistry/schemaregistries/schemas\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schema\" }\n ,\"microsoft.deviceregistry/schemaregistries/schemas/schemaversions\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schemas schema version\" }\n ,\"microsoft.devices/iothubs\": { \"SingularDisplayName\": \"IoT hub\" }\n ,\"microsoft.devices/provisioningservices\": { \"SingularDisplayName\": \"Azure IoT Hub Device Provisioning Service (DPS)\" }\n ,\"microsoft.deviceupdate/accounts\": { \"SingularDisplayName\": \"Device Update for IoT Hub\" }\n ,\"microsoft.deviceupdate/updateaccounts\": { \"SingularDisplayName\": \"Device Update Account\" }\n ,\"microsoft.deviceupdate/updateaccounts/activedeployments\": { \"SingularDisplayName\": \"Device Update Active Deployment\" }\n ,\"microsoft.deviceupdate/updateaccounts/agents\": { \"SingularDisplayName\": \"Device Update Agent\" }\n ,\"microsoft.deviceupdate/updateaccounts/deployments\": { \"SingularDisplayName\": \"Device Update Deployment\" }\n ,\"microsoft.deviceupdate/updateaccounts/deviceclasses\": { \"SingularDisplayName\": \"Device Update Device Class\" }\n ,\"microsoft.deviceupdate/updateaccounts/updates\": { \"SingularDisplayName\": \"Device Update\" }\n ,\"microsoft.devops/pipelines\": { \"SingularDisplayName\": \"Microsoft.DevOps pipeline\" }\n ,\"microsoft.devopsinfrastructure/pools\": { \"SingularDisplayName\": \"Managed DevOps Pool\" }\n ,\"microsoft.devspaces/controllers\": { \"SingularDisplayName\": \"Microsoft.DevSpaces controller\" }\n ,\"microsoft.devtestlab/labs\": { \"SingularDisplayName\": \"DevTest lab\" }\n ,\"microsoft.devtestlab/labs/virtualmachines\": { \"SingularDisplayName\": \"DevTest Lab virtual machine\" }\n ,\"microsoft.devtestlab/schedules\": { \"SingularDisplayName\": \"Microsoft.DevTestLab schedule\" }\n ,\"microsoft.devtunnels/tunnelplans\": { \"SingularDisplayName\": \"Dev Tunnels Domain\" }\n ,\"microsoft.diagnostics/apollo\": { \"SingularDisplayName\": \"Microsoft.Diagnostics apollo\" }\n ,\"microsoft.digitaltwins/digitaltwinsinstances\": { \"SingularDisplayName\": \"Azure Digital Twins\" }\n ,\"microsoft.discovery/agents\": { \"SingularDisplayName\": \"Microsoft Discovery Agent\" }\n ,\"microsoft.discovery/bookshelves\": { \"SingularDisplayName\": \"Microsoft Discovery Bookshelf\" }\n ,\"microsoft.discovery/datacontainers\": { \"SingularDisplayName\": \"Microsoft Discovery Data Container\" }\n ,\"microsoft.discovery/datacontainers/dataassets\": { \"SingularDisplayName\": \"Data asset\" }\n ,\"microsoft.discovery/models\": { \"SingularDisplayName\": \"Microsoft Discovery Model\" }\n ,\"microsoft.discovery/storages\": { \"SingularDisplayName\": \"Microsoft Discovery Storage\" }\n ,\"microsoft.discovery/supercomputers\": { \"SingularDisplayName\": \"Microsoft Discovery Supercomputer\" }\n ,\"microsoft.discovery/supercomputers/nodepools\": { \"SingularDisplayName\": \"Nodepool\" }\n ,\"microsoft.discovery/tools\": { \"SingularDisplayName\": \"Microsoft Discovery Tool\" }\n ,\"microsoft.discovery/workflows\": { \"SingularDisplayName\": \"Microsoft Discovery Workflow\" }\n ,\"microsoft.discovery/workspaces\": { \"SingularDisplayName\": \"Microsoft Discovery Workspace\" }\n ,\"microsoft.discovery/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft Discovery Project\" }\n ,\"microsoft.documentdb/cassandraclusters\": { \"SingularDisplayName\": \"Azure Managed Instance for Apache Cassandra\" }\n ,\"microsoft.documentdb/databaseaccounts\": { \"SingularDisplayName\": \"Cosmos DB account\" }\n ,\"microsoft.documentdb/fleets\": { \"SingularDisplayName\": \"Azure Cosmos DB Fleet\" }\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccounts\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccountswithlocations\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\n ,\"microsoft.documentdb/mongoclusters\": { \"SingularDisplayName\": \"Azure Cosmos DB for MongoDB (vCore)\" }\n ,\"microsoft.documentdb/throughputpools\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pool\" }\n ,\"microsoft.documentdb/throughputpools/throughputpoolaccounts\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pools throughput pool account\" }\n ,\"microsoft.domainregistration/domains\": { \"SingularDisplayName\": \"App Service Domain\" }\n ,\"microsoft.domainregistration/topleveldomains\": { \"SingularDisplayName\": \"Microsoft.DomainRegistration top level domain\" }\n ,\"microsoft.durabletask/namespaces\": { \"SingularDisplayName\": \"Microsoft.DurableTask namespace\" }\n ,\"microsoft.durabletask/namespaces/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\n ,\"microsoft.durabletask/schedulers\": { \"SingularDisplayName\": \"Durable Task Scheduler\" }\n ,\"microsoft.durabletask/schedulers/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\n ,\"microsoft.dynamics365fraudprotection/instances\": { \"SingularDisplayName\": \"Microsoft.Dynamics365FraudProtection instance\" }\n ,\"microsoft.easm/workspaces\": { \"SingularDisplayName\": \"Microsoft Defender EASM\" }\n ,\"microsoft.edge/configurations\": { \"SingularDisplayName\": \"Site configuration\" }\n ,\"microsoft.edge/configurations/arcgatewayconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations arc gateway configuration\" }\n ,\"microsoft.edge/configurations/connectivityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations connectivity configuration\" }\n ,\"microsoft.edge/configurations/dynamicconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configuration\" }\n ,\"microsoft.edge/configurations/dynamicconfigurations/versions\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configurations version\" }\n ,\"microsoft.edge/configurations/networkconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations network configuration\" }\n ,\"microsoft.edge/configurations/securityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations security configuration\" }\n ,\"microsoft.edge/configurations/timeserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations time server configuration\" }\n ,\"microsoft.edge/connectivitystatuses\": { \"SingularDisplayName\": \"Microsoft.Edge connectivity statuse\" }\n ,\"microsoft.edge/disconnectedoperations\": { \"SingularDisplayName\": \"Azure Local - disconnected operations\" }\n ,\"microsoft.edge/siteawareresourcetypes\": { \"SingularDisplayName\": \"Microsoft.Edge site aware resource type\" }\n ,\"microsoft.edge/sites\": { \"SingularDisplayName\": \"Site manager - Azure Arc\" }\n ,\"microsoft.edge/updates\": { \"SingularDisplayName\": \"Microsoft.Edge update\" }\n ,\"microsoft.edgemarketplace/offers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace offer\" }\n ,\"microsoft.edgemarketplace/publishers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace publisher\" }\n ,\"microsoft.edgeorder/addresses\": { \"SingularDisplayName\": \"Azure Edge Hardware Center Address\" }\n ,\"microsoft.edgeorder/bootstrapconfigurations\": { \"SingularDisplayName\": \"Site Key\" }\n ,\"microsoft.edgeorder/orderitems\": { \"SingularDisplayName\": \"Azure Edge Hardware Center\" }\n ,\"microsoft.edgeorder/virtual_orderitems\": { \"SingularDisplayName\": \"Device\" }\n ,\"microsoft.edgezones/extendedzones\": { \"SingularDisplayName\": \"Microsoft.EdgeZones extended zone\" }\n ,\"microsoft.education/grants\": { \"SingularDisplayName\": \"Microsoft.Education grant\" }\n ,\"microsoft.education/labs\": { \"SingularDisplayName\": \"Microsoft.Education lab\" }\n ,\"microsoft.education/labs/joinrequests\": { \"SingularDisplayName\": \"Microsoft.Education labs join request\" }\n ,\"microsoft.education/labs/students\": { \"SingularDisplayName\": \"Microsoft.Education labs student\" }\n ,\"microsoft.education/studentlabs\": { \"SingularDisplayName\": \"Microsoft.Education student lab\" }\n ,\"microsoft.elastic/monitors\": { \"SingularDisplayName\": \"Elastic Cloud Resource\" }\n ,\"microsoft.elasticsan/elasticsans\": { \"SingularDisplayName\": \"Elastic SAN\" }\n ,\"microsoft.energydataplatform/energyservices\": { \"SingularDisplayName\": \"Microsoft.EnergyDataPlatform energy service\" }\n ,\"microsoft.enterpriseknowledgegraph/services\": { \"SingularDisplayName\": \"Microsoft.EnterpriseKnowledgeGraph service\" }\n ,\"microsoft.enterprisesupport/enterprisesupports\": { \"SingularDisplayName\": \"Microsoft.EnterpriseSupport enterprise support\" }\n ,\"microsoft.eventgrid/domains\": { \"SingularDisplayName\": \"Event Grid Domain\" }\n ,\"microsoft.eventgrid/domains/topics\": { \"SingularDisplayName\": \"Event Grid Domain Topic\" }\n ,\"microsoft.eventgrid/eventsubscriptions\": { \"SingularDisplayName\": \"Microsoft.EventGrid event subscription\" }\n ,\"microsoft.eventgrid/extensiontopics\": { \"SingularDisplayName\": \"Event Grid extension topic\" }\n ,\"microsoft.eventgrid/namespaces\": { \"SingularDisplayName\": \"Event Grid Namespace\" }\n ,\"microsoft.eventgrid/namespaces/topics\": { \"SingularDisplayName\": \"Event Grid Namespace Topic\" }\n ,\"microsoft.eventgrid/namespaces/topics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Subscription\" }\n ,\"microsoft.eventgrid/namespaces/topicspaces\": { \"SingularDisplayName\": \"Event Grid Topic Space\" }\n ,\"microsoft.eventgrid/partnerconfigurations\": { \"SingularDisplayName\": \"Event Grid Partner Configuration\" }\n ,\"microsoft.eventgrid/partnerdestinations\": { \"SingularDisplayName\": \"Event Grid Partner Destination\" }\n ,\"microsoft.eventgrid/partnernamespaces\": { \"SingularDisplayName\": \"Event Grid Partner Namespace\" }\n ,\"microsoft.eventgrid/partnernamespaces/channels\": { \"SingularDisplayName\": \"Event Grid Channel\" }\n ,\"microsoft.eventgrid/partnerregistrations\": { \"SingularDisplayName\": \"Event Grid Partner Registration\" }\n ,\"microsoft.eventgrid/partnertopics\": { \"SingularDisplayName\": \"Event Grid Partner Topic\" }\n ,\"microsoft.eventgrid/systemtopics\": { \"SingularDisplayName\": \"Event Grid System Topic\" }\n ,\"microsoft.eventgrid/systemtopics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Grid Subscriptions\" }\n ,\"microsoft.eventgrid/topics\": { \"SingularDisplayName\": \"Event Grid Topic\" }\n ,\"microsoft.eventgrid/topictypes\": { \"SingularDisplayName\": \"Microsoft.EventGrid topic type\" }\n ,\"microsoft.eventgrid/verifiedpartners\": { \"SingularDisplayName\": \"Microsoft.EventGrid verified partner\" }\n ,\"microsoft.eventhub/clusters\": { \"SingularDisplayName\": \"Event Hubs Cluster\" }\n ,\"microsoft.eventhub/namespaces\": { \"SingularDisplayName\": \"Event Hubs namespace\" }\n ,\"microsoft.eventhub/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Event Hubs Geo-DR Alias\" }\n ,\"microsoft.eventhub/namespaces/eventhubs\": { \"SingularDisplayName\": \"Event Hubs Instance\" }\n ,\"microsoft.eventhub/namespaces/providers/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\n ,\"microsoft.eventhub/namespaces/schemagroups\": { \"SingularDisplayName\": \"Schema Group\" }\n ,\"microsoft.experimentation/experimentworkspaces\": { \"SingularDisplayName\": \"Experiment Workspace\" }\n ,\"microsoft.extendedlocation/customlocations\": { \"SingularDisplayName\": \"Custom location\" }\n ,\"microsoft.fabric/capacities\": { \"SingularDisplayName\": \"Fabric Capacity\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/operationresults\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric operation result\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private endpoint connection\" }\n ,\"microsoft.fabric/privatelinkservicesforfabric/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private link resource\" }\n ,\"microsoft.fairfieldgardens/deviceprovisioningstates\": { \"SingularDisplayName\": \"Microsoft.FairfieldGardens device provisioning state\" }\n ,\"microsoft.fairfieldgardens/provisioningresources\": { \"SingularDisplayName\": \"Fairfield Gardens\" }\n ,\"microsoft.fairfieldgardens/provisioningresources/provisioningpolicies\": { \"SingularDisplayName\": \"Provisioning policy\" }\n ,\"microsoft.falcon/namespaces\": { \"SingularDisplayName\": \"Microsoft.Falcon namespace\" }\n ,\"microsoft.features/featureprovidernamespaces/featureconfigurations\": { \"SingularDisplayName\": \"Preview features\" }\n ,\"microsoft.fidalgo/devcenters\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenter\" }\n ,\"microsoft.fidalgo/devcenters/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters attachednetwork\" }\n ,\"microsoft.fidalgo/devcenters/catalogs\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalog\" }\n ,\"microsoft.fidalgo/devcenters/catalogs/items\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalogs item\" }\n ,\"microsoft.fidalgo/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters devboxdefinition\" }\n ,\"microsoft.fidalgo/devcenters/environmenttypes\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters environment type\" }\n ,\"microsoft.fidalgo/devcenters/galleries\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters gallery\" }\n ,\"microsoft.fidalgo/devcenters/galleries/images\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries image\" }\n ,\"microsoft.fidalgo/devcenters/galleries/images/versions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries images version\" }\n ,\"microsoft.fidalgo/devcenters/mappings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters mapping\" }\n ,\"microsoft.fidalgo/machinedefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo machinedefinition\" }\n ,\"microsoft.fidalgo/networksettings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksetting\" }\n ,\"microsoft.fidalgo/networksettings/healthchecks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksettings healthcheck\" }\n ,\"microsoft.fidalgo/projects\": { \"SingularDisplayName\": \"Microsoft.Fidalgo project\" }\n ,\"microsoft.fidalgo/projects/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects attachednetwork\" }\n ,\"microsoft.fidalgo/projects/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects devboxdefinition\" }\n ,\"microsoft.fidalgo/projects/environments\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects environment\" }\n ,\"microsoft.fidalgo/projects/pools\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects pool\" }\n ,\"microsoft.fileshares/fileshares\": { \"SingularDisplayName\": \"File share\" }\n ,\"microsoft.fluidrelay/fluidrelayservers\": { \"SingularDisplayName\": \"Fluid Relay\" }\n ,\"microsoft.footprintmonitoring/profiles\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profile\" }\n ,\"microsoft.footprintmonitoring/profiles/experiments\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles experiment\" }\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoint\" }\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints/conditions\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoints condition\" }\n ,\"microsoft.gallery/myareas/galleryitems\": { \"SingularDisplayName\": \"Template\" }\n ,\"microsoft.genomics/accounts\": { \"SingularDisplayName\": \"Genomics account\" }\n ,\"microsoft.graph/azureadapplication\": { \"SingularDisplayName\": \"Entra application\" }\n ,\"microsoft.graph/azureadapplicationprototype\": { \"SingularDisplayName\": \"Microsoft.Graph Azure ad application prototype\" }\n ,\"microsoft.graphservices/accounts\": { \"SingularDisplayName\": \"Metered API account\" }\n ,\"microsoft.guestconfiguration/guestconfigurationassignments\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignment\" }\n ,\"microsoft.guestconfiguration/guestconfigurationassignments/reports\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignments report\" }\n ,\"microsoft.hanaonazure/hanainstances\": { \"SingularDisplayName\": \"SAP HANA on Azure\" }\n ,\"microsoft.hanaonazure/sapmonitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP Solutions (classic)\" }\n ,\"microsoft.hardware/orders\": { \"SingularDisplayName\": \"Microsoft.Hardware order\" }\n ,\"microsoft.hardwaresecuritymodules/cloudhsmclusters\": { \"SingularDisplayName\": \"Azure Cloud HSM\" }\n ,\"microsoft.hdinsight/clusterpools\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster pool\" }\n ,\"microsoft.hdinsight/clusterpools/clusters\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster\" }\n ,\"microsoft.hdinsight/clusterpools/clusters/instanceviews\": { \"SingularDisplayName\": \"Microsoft.HDInsight clusterpools clusters instance view\" }\n ,\"microsoft.hdinsight/clusters\": { \"SingularDisplayName\": \"HDInsight cluster\" }\n ,\"microsoft.healthbot/healthbots\": { \"SingularDisplayName\": \"Healthcare agent service\" }\n ,\"microsoft.healthcareapis/services\": { \"SingularDisplayName\": \"Azure API for FHIR\" }\n ,\"microsoft.healthcareapis/workspaces\": { \"SingularDisplayName\": \"Health Data Services workspace\" }\n ,\"microsoft.healthcareapis/workspaces/dicomservices\": { \"SingularDisplayName\": \"DICOM service\" }\n ,\"microsoft.healthcareapis/workspaces/fhirservices\": { \"SingularDisplayName\": \"FHIR service\" }\n ,\"microsoft.healthcareapis/workspaces/iotconnectors\": { \"SingularDisplayName\": \"MedTech service\" }\n ,\"microsoft.healthdataaiservices/deidservices\": { \"SingularDisplayName\": \"De-identification Service\" }\n ,\"microsoft.healthmodel/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\n ,\"microsoft.healthplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.HealthPlatform account\" }\n ,\"microsoft.help/diagnostics\": { \"SingularDisplayName\": \"Microsoft.Help diagnostic\" }\n ,\"microsoft.help/selfhelp\": { \"SingularDisplayName\": \"Microsoft.Help self help\" }\n ,\"microsoft.help/simplifiedsolutions\": { \"SingularDisplayName\": \"Microsoft.Help simplified solution\" }\n ,\"microsoft.help/solutions\": { \"SingularDisplayName\": \"Microsoft.Help solution\" }\n ,\"microsoft.help/troubleshooters\": { \"SingularDisplayName\": \"Microsoft.Help troubleshooter\" }\n ,\"microsoft.hpcworkbench/instances\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instance\" }\n ,\"microsoft.hpcworkbench/instances/chambers\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chamber\" }\n ,\"microsoft.hpcworkbench/instances/chambers/accessprofiles\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers access profile\" }\n ,\"microsoft.hpcworkbench/instances/chambers/filerequests\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file request\" }\n ,\"microsoft.hpcworkbench/instances/chambers/files\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file\" }\n ,\"microsoft.hpcworkbench/instances/chambers/storages\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers storage\" }\n ,\"microsoft.hpcworkbench/instances/chambers/workloads\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers workload\" }\n ,\"microsoft.hpcworkbench/instances/consortiums\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances consortium\" }\n ,\"microsoft.hybridcloud/cloudconnections\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connection\" }\n ,\"microsoft.hybridcloud/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connector\" }\n ,\"microsoft.hybridcompute/arcgatewayassociatedresources\": { \"SingularDisplayName\": \"Arc gateway associated resource\" }\n ,\"microsoft.hybridcompute/arcserverwithwac\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/gateways\": { \"SingularDisplayName\": \"Arc gateway\" }\n ,\"microsoft.hybridcompute/licenses\": { \"SingularDisplayName\": \"Extended Security Updates - Windows Server 2012/R2\" }\n ,\"microsoft.hybridcompute/machines\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machines/microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\n ,\"microsoft.hybridcompute/machines/microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\n ,\"microsoft.hybridcompute/machines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\n ,\"microsoft.hybridcompute/machinesesu\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinespaygo\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinessoftwareassurance\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/machinessovereign\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\n ,\"microsoft.hybridcompute/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Arc Private Link Scope\" }\n ,\"microsoft.hybridcompute/settings\": { \"SingularDisplayName\": \"Microsoft.HybridCompute setting\" }\n ,\"microsoft.hybridconnectivity/endpoints\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoint\" }\n ,\"microsoft.hybridconnectivity/endpoints/serviceconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoints service configuration\" }\n ,\"microsoft.hybridconnectivity/publiccloudconnectors\": { \"SingularDisplayName\": \"Multicloud connector\" }\n ,\"microsoft.hybridconnectivity/solutionconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configuration\" }\n ,\"microsoft.hybridconnectivity/solutionconfigurations/inventory\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configurations inventory\" }\n ,\"microsoft.hybridconnectivity/solutiontypes\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution type\" }\n ,\"microsoft.hybridcontainerservice/kubernetesversions\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService kubernetes version\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instance\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/agentpools\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances agent pool\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances hybrid identity metadata\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances upgrade profile\" }\n ,\"microsoft.hybridcontainerservice/provisionedclusters\": { \"SingularDisplayName\": \"Kubernetes hybrid - Azure Arc\" }\n ,\"microsoft.hybridcontainerservice/skus\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService SKU\" }\n ,\"microsoft.hybridcontainerservice/storagespaces\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService storage space\" }\n ,\"microsoft.hybridcontainerservice/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService virtual network\" }\n ,\"microsoft.hybriddata/datamanagers\": { \"SingularDisplayName\": \"Microsoft.HybridData data manager\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data service\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definition\" }\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions/jobs\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definitions job\" }\n ,\"microsoft.hybriddata/datamanagers/datastores\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store\" }\n ,\"microsoft.hybriddata/datamanagers/datastoretypes\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store type\" }\n ,\"microsoft.hybriddata/datamanagers/publickeys\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers public key\" }\n ,\"microsoft.hybridnetwork/configurationgroupvalues\": { \"SingularDisplayName\": \"Configuration Group Value\" }\n ,\"microsoft.hybridnetwork/devices\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Device\" }\n ,\"microsoft.hybridnetwork/networkfunctions\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Network Function\" }\n ,\"microsoft.hybridnetwork/proxypublishers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publisher\" }\n ,\"microsoft.hybridnetwork/proxypublishers/artifactstores\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers artifact store\" }\n ,\"microsoft.hybridnetwork/proxypublishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers configuration group schema\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition group\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition groups network function definition version\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design group\" }\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design groups network service design version\" }\n ,\"microsoft.hybridnetwork/publishers\": { \"SingularDisplayName\": \"Publisher\" }\n ,\"microsoft.hybridnetwork/publishers/artifactstores\": { \"SingularDisplayName\": \"Publisher Artifact Store\" }\n ,\"microsoft.hybridnetwork/publishers/artifactstores/artifactmanifests\": { \"SingularDisplayName\": \"Publisher Artifact Manifest\" }\n ,\"microsoft.hybridnetwork/publishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Configuration Group Schema\" }\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Network Function Definition\" }\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Network Function Definition Version\" }\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Network Service Design\" }\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Network Service Design Version\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management container\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rolloutsequences\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout sequence\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rollouttiers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout tier\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specification\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollout\" }\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts/statuses\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollouts statuse\" }\n ,\"microsoft.hybridnetwork/sitenetworkservices\": { \"SingularDisplayName\": \"Site Network Service\" }\n ,\"microsoft.hybridnetwork/sites\": { \"SingularDisplayName\": \"Site\" }\n })[tolower(id)]\n}\n", - "$fxv#10": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Prices |=========================================================================================================\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_transform_v1_2 function\n.create-or-alter function\nwith (docstring='Transforms Prices_raw into FOCUS 1.2.', folder='Prices')\nPrices_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n let prices = materialize(\n Prices_raw\n | extend PricingCurrency = coalesce(Currency, CurrencyCode) // CurrencyCode last as a fallback only\n | extend x_SkuId = coalesce(SkuId, SkuID)\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\n | extend x_SkuTerm = isoMonths(Term)\n | project-rename\n SkuMeter = MeterName,\n x_BaseUnitPrice = BasePrice,\n x_EffectivePeriodEnd = EffectiveEndDate,\n x_EffectivePeriodStart = EffectiveStartDate,\n x_PricingUnitDescription = UnitOfMeasure,\n x_SkuIncludedQuantity = IncludedQuantity,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuMeterType = MeterType,\n x_SkuOfferId = OfferID,\n x_SkuPartNumber = PartNumber,\n x_SkuPriceType = PriceType,\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTier = TierMinimumUnits\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, real(null)) // UnitPrice for savings plan is not the on-demand unit price\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, real(null)) // MarketPrice for savings plan is not the list price\n | extend ChargeCategory = case(\n x_SkuPriceType == 'Consumption', 'Usage',\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\n ''\n )\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\n //\n // Get latest ingested row based on the unique ID\n | extend x_IngestionTime = ingestion_time()\n );\n //\n // Meters for reservations and savings plans to identify commitment eligibility\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\n //\n // Copy list/base/contracted prices from on-demand SKUs\n prices\n | where x_SkuPriceType == 'SavingsPlan'\n // If we use join, specify the shuffle key\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\n //\n // Set CommitmentDiscountCategory for reuse\n | extend CommitmentDiscountCategory = case(\n x_SkuPriceType == 'ReservedInstance', 'Usage',\n x_SkuPriceType == 'SavingsPlan', 'Spend',\n ''\n )\n //\n // Calculate commitment discount eligibility\n // TODO: Would a join be faster?\n // TODO: Check this to ensure it's correct\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\n //\n // TODO: Implement x_CommitmentDiscountNormalizedRatio\n | extend x_CommitmentDiscountNormalizedRatio = real(null)\n //\n // Add PricingUnit and x_PricingBlockSize\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\n | lookup kind=leftouter (PricingUnits) on x_PricingUnitDescription\n //\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, real(null)) // Savings plan prices are for the effective price, not the contracted price\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\n | project\n BillingAccountId = tolower(case(\n BillingProfileId startswith '/', BillingProfileId,\n BillingAccountId startswith '/', BillingAccountId,\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\n )),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType = case(\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\n ''\n ),\n CommitmentDiscountUnit = case(\n isempty(CommitmentDiscountCategory), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), PricingUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', PricingUnit),\n ''\n ),\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory = case(\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed',\n ''\n ),\n PricingCurrency,\n PricingUnit,\n SkuId = coalesce(ProductId, ProductID),\n SkuMeter,\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement = case(\n strlen(x_BillingAccountId) > 32, 'MCA',\n strlen(x_BillingAccountId) < 32, 'EA',\n 'Unknown'\n ),\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingSubcategory = case(\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\n ''\n ),\n x_PricingUnitDescription,\n x_SkuDescription = Product,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\n}\n\n// Prices_final_v1_2 table\n.create-merge table Prices_final_v1_2 (\n BillingAccountId: string,\n BillingAccountName: string,\n BillingCurrency: string,\n ChargeCategory: string,\n CommitmentDiscountCategory: string,\n CommitmentDiscountType: string,\n CommitmentDiscountUnit: string,\n ContractedUnitPrice: real,\n ListUnitPrice: real,\n PricingCategory: string,\n PricingCurrency: string, // Azure\n PricingUnit: string,\n SkuId: string,\n SkuMeter: string, // Azure\n SkuPriceId: string,\n SkuPriceIdv2: string, // Hubs add-on\n x_BaseUnitPrice: real, // Azure\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure MCA\n x_BillingProfileId: string, // Azure MCA\n x_CommitmentDiscountNormalizedRatio: real, // Hubs add-on\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_ContractedUnitPriceDiscount: real, // Hubs add-on\n x_ContractedUnitPriceDiscountPercent: real, // Hubs add-on\n x_EffectivePeriodEnd: datetime, // Azure\n x_EffectivePeriodStart: datetime, // Azure\n x_EffectiveUnitPrice: real, // Azure\n x_EffectiveUnitPriceDiscount: real, // Hubs add-on\n x_EffectiveUnitPriceDiscountPercent: real, // Hubs add-on\n x_IngestionTime: datetime, // Hubs add-on\n x_PricingBlockSize: real, // Hubs add-on\n x_PricingSubcategory: string, // Hubs add-on\n x_PricingUnitDescription: string, // Azure\n x_SkuDescription: string, // Azure\n x_SkuId: string, // Azure\n x_SkuIncludedQuantity: real, // Azure EA\n x_SkuMeterCategory: string, // Azure\n x_SkuMeterId: string, // Azure\n x_SkuMeterSubcategory: string, // Azure\n x_SkuMeterType: string, // Azure\n x_SkuPriceType: string, // Azure\n x_SkuProductId: string, // Azure\n x_SkuRegion: string, // Azure\n x_SkuServiceFamily: string, // Azure\n x_SkuOfferId: string, // Azure EA\n x_SkuPartNumber: string, // Azure EA\n x_SkuTerm: int, // Azure\n x_SkuTier: real, // Azure MCA\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_TotalUnitPriceDiscount: real, // Hubs add-on\n x_TotalUnitPriceDiscountPercent: real // Hubs add-on\n)\n\n// Update policy for Prices_raw -> Prices_final_v1_2\n.alter table Prices_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Prices_raw\",\n \"Query\": \"Prices_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Cost and usage |=================================================================================================\n// Supported versions:\n// - MS: 1.2-preview, 1.0, 1.0-preview(v1)\n// https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0\n// https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024\n// https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 \n// https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All costs transformed to FOCUS 1.2.', folder='Costs')\nCosts_transform_v1_2()\n{\n let checkString = (column: string, oldValue: string, newValue: string) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n let checkInt = (column: string, oldValue: int, newValue: int) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n let checkReal = (column: string, oldValue: real, newValue: real) {\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\n };\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n Costs_raw\n //\n // Dedupe rows\n | extend x_IngestionTime = ingestion_time()\n | extend x_ChargeId = ''\n // TODO: Consider adding a unique charge ID per row\n // hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // // 1. Resource hierarchy (including resource name), highest to lowest\n // BillingAccountId,\n // x_InvoiceSectionId,\n // x_AccountOwnerId,\n // SubAccountId,\n // x_ResourceGroupName,\n // ResourceName,\n // // 2. Resource details\n // ResourceId,\n // RegionId,\n // Tags,\n // CommitmentDiscountId,\n // x_CostCenter,\n // // 4. Meter details\n // SkuPriceId,\n // x_SkuMeterId,\n // x_SkuPartNumber,\n // x_SkuOfferId,\n // x_SkuDetails,\n // // 5. Date\n // ChargePeriodStart\n // ))\n //\n // Identify data quality issues\n // TODO: Remove x_SourceChanges in v1_3 (or later)\n | extend x_SourceChanges = trim_end(',', strcat(\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\n 'XEffectiveUnitPriceRoundingError,', ''),\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\n ))\n //\n // Handle provider columns that moved to FOCUS\n | extend PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency)\n //\n // Backup original prices/costs before the merge\n | extend old_ContractedCost = ContractedCost\n | extend old_ContractedUnitPrice = ContractedUnitPrice\n | extend old_ListCost = ListCost\n | extend old_ListUnitPrice = ListUnitPrice\n | extend old_x_EffectiveUnitPrice = x_EffectiveUnitPrice\n //\n // Fix columns needed in other changes\n | extend old_ProviderName = ProviderName, ProviderName = case(\n isnotempty(ProviderName), ProviderName,\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\n ''\n )\n //\n // Identify source\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\n ''\n ))\n // Append version check error code\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\n )\n //\n // Fix quantities\n | extend old_PricingQuantity = PricingQuantity, PricingQuantity = case(\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\n PricingQuantity\n )\n | extend old_ConsumedQuantity = ConsumedQuantity, ConsumedQuantity = case(\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\n ConsumedQuantity\n )\n //\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\n and (isempty(ListUnitPrice) or isempty(ContractedUnitPrice) or ListUnitPrice == 0 or ContractedUnitPrice == 0)\n and x_EffectiveUnitPrice != 0\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\n | as allCosts\n | where tmp_MissingPrices\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | as costsWithMissingPrices\n | join kind=leftouter (\n Prices_final_v1_2\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\n ) on tmp_ReservationPriceLookupKey\n //\n // Select the best price to use for each row\n | extend x_EffectiveUnitPrice = case(\n // If price is a rounding error away from the billed price, use the billed price\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\n // If price is a rounding error away from the contracted price, use the contracted price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\n x_EffectiveUnitPrice\n )\n | extend ContractedUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\n x_EffectiveUnitPrice\n )\n | extend ListUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // Otherwise, assume the contracted price is the same as list price to support aggregations\n ContractedUnitPrice\n )\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\n | extend ContractedCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\n // ContractedCost is 0 in all other scenarios...\n // If 0 and there's a billed cost and prices are the same, use BilledCost\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume EffectiveCost\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\n // Fall back to the original value for any unhandled scenarios\n ContractedCost\n )\n | extend ListCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\n // ListCost is 0 in all other scenarios...\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume ContractedCost\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\n // Fall back to the original value for any unhandled scenarios\n ListCost\n )\n // Merge the rest of the unmodified cost records and remove excess columns\n | union (allCosts | where not(tmp_MissingPrices))\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\n //\n | extend SkuPriceDetails = parse_json(SkuPriceDetails)\n | extend Tags = parse_json(Tags)\n | extend x_SkuDetails = parse_json(x_SkuDetails)\n //\n // Handle FOCUS 1.0-preview\n | extend old_ChargeSubcategory = ChargeSubcategory\n | extend old_ChargeCategory = ChargeCategory, ChargeCategory = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Credit', 'Credit',\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\n ChargeCategory\n )\n | extend old_ChargeClass = ChargeClass, ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass)\n //\n // Populate CapacityReservationId when not specified\n | extend CapacityReservationId = coalesce(CapacityReservationId, tostring(coalesce(x_SkuDetails.VMCapacityReservationId, SkuPriceDetails.VMCapacityReservationId, SkuPriceDetails.x_VMCapacityReservationId)))\n | extend old_CapacityReservationStatus = CapacityReservationStatus, CapacityReservationStatus = case(\n isempty(CapacityReservationId), '',\n isnotempty(CapacityReservationStatus), CapacityReservationStatus,\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\n 'Used'\n )\n //\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\n | extend old_ChargeFrequency = ChargeFrequency, ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency)\n //\n // Commitment discounts\n | extend x_CommitmentDiscountNormalizedRatio = case(\n // Calculate from CommitmentDiscountQuantity, if specified\n isnotempty(CommitmentDiscountQuantity) and CommitmentDiscountQuantity != 0, CommitmentDiscountQuantity / PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\n // Not applicable\n isempty(CommitmentDiscountStatus), real(null),\n // Parse from SKU details if not specified explicitly\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, SkuPriceDetails.RINormalizationRatio, SkuPriceDetails.x_RINormalizationRatio, dynamic(1)))\n )\n | extend old_CommitmentDiscountQuantity = CommitmentDiscountQuantity, CommitmentDiscountQuantity = case(\n // FOCUS 1.2\n isnotempty(CommitmentDiscountQuantity), CommitmentDiscountQuantity,\n // FOCUS 1.0-preview, 1.0\n isempty(CommitmentDiscountStatus), real(null),\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\n real(null)\n )\n | extend old_CommitmentDiscountUnit = CommitmentDiscountUnit, CommitmentDiscountUnit = case(\n // FOCUS 1.2\n isnotempty(CommitmentDiscountUnit), CommitmentDiscountUnit,\n // FOCUS 1.0\n isempty(CommitmentDiscountQuantity), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\n ''\n )\n | extend old_CommitmentDiscountStatus = CommitmentDiscountStatus, CommitmentDiscountStatus = case(\n // FOCUS 1.0+\n isnotempty(CommitmentDiscountStatus), CommitmentDiscountStatus,\n // FOCUS 1.0-preview\n ChargeSubcategory == 'Used Commitment', 'Used',\n ChargeSubcategory == 'Unused Commitment', 'Unused',\n ''\n )\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n //\n // Pricing\n | extend old_x_AmortizationClass = x_AmortizationClass, x_AmortizationClass = case(\n // FOCUS 1.2\n isnotempty(x_AmortizationClass), x_AmortizationClass,\n // FOCUS 1.0-preview+\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\n ''\n )\n | extend old_PricingCategory = PricingCategory, PricingCategory = case(\n // FOCUS 1.0+\n isnotempty(PricingCategory), PricingCategory,\n // FOCUS 1.0-preview\n PricingCategory == 'On-Demand', 'Standard',\n PricingCategory == 'Commitment-Based', 'Committed',\n ''\n )\n //\n // Commitment discount utilization\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n //\n // BUG: Fix ContractedCost that has bad values\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\n //\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), real(null))\n | extend old_ConsumedUnit = ConsumedUnit, ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\n //\n // Convert IDs to lowercase for consistency\n | extend BillingAccountId = tolower(BillingAccountId)\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\n //\n // BUG: Remove EffectiveCost for commitment discount purchases\n | extend old_EffectiveCost = EffectiveCost, EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), EffectiveCost)\n | extend old_x_EffectiveCostInUsd = x_EffectiveCostInUsd, x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), x_EffectiveCostInUsd)\n //\n // Clean up resource columns\n | extend old_ResourceId = ResourceId, ResourceId = case(\n isnotempty(ResourceId), ResourceId,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\n ResourceId\n )\n | extend old_ResourceName = ResourceName, ResourceName = tolower(case(\n isnotempty(ResourceName), ResourceName,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\n ResourceName\n ))\n | extend old_x_ResourceType = x_ResourceType, x_ResourceType = case(\n isnotempty(x_ResourceType), x_ResourceType,\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\n x_ResourceType\n )\n | extend old_ResourceType = ResourceType, ResourceType = case(\n // Use existing resource type display name unless it's an internal resource type ID\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\n // Use CommitmentDiscountType for commitment discount purchases\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\n // Look up display name from internal type\n isnotempty(x_ResourceType), coalesce(tostring(resource_type(x_ResourceType).SingularDisplayName), ResourceType, x_ResourceType),\n ResourceType\n )\n //\n // Handle missing values\n | extend old_PublisherName = PublisherName, PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, '')\n //\n // Handle FOCUS 1.0-preview Region column\n | extend old_Region = Region\n | extend old_RegionId = RegionId, RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region))\n | extend RegionName = coalesce(RegionName, Region)\n //\n // SKU properties\n | extend x_SkuCoreCount = toint(coalesce(SkuPriceDetails.CoreCount, SkuPriceDetails.x_VCPUs, x_SkuDetails.VCPUs, SkuPriceDetails.x_VCores, x_SkuDetails.VCores, SkuPriceDetails.x_vCores, x_SkuDetails.vCores))\n | extend x_SkuInstanceType = tostring(coalesce(SkuPriceDetails.InstanceType, SkuPriceDetails.x_ServiceType, x_SkuDetails.ServiceType, SkuPriceDetails.x_ServerSku, x_SkuDetails.ServerSku))\n | extend x_SkuOperatingSystem = case(\n isnotempty(SkuPriceDetails.OperatingSystem), SkuPriceDetails.OperatingSystem,\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Canonical', 'Linux',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType)\n )\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\n | extend SkuPriceDetails = case(\n // FOCUS 1.2\n isnotempty(SkuPriceDetails), SkuPriceDetails,\n // FOCUS 1.0-preview, 1.0\n parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\n // Prefix all keys with x_ first to avoid double-prefixing\n , @'([\\{,])\"', @'\\1\"x_')\n // CoreCount for number of CPUs/vCPUs/cores/vCores\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\n // TODO: DiskSpace for disk size in GiB\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\n // TODO: GpuCount for the number of GPUs\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\n // TODO: InstanceSeries for the size family/series\n // TODO: MemorySize for the RAM in GiB\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\n // OperatingSystem for the OS name\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\n )\n )\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\n SkuPriceDetails)\n //\n // Azure Hybrid Benefit\n | extend tmp_SqlAhb = tolower(coalesce(x_SkuDetails.AHB, SkuPriceDetails.x_AHB))\n | extend x_SkuLicenseType = case(\n ChargeCategory != 'Usage', '',\n x_SkuMeterCategory in ('Virtual Machines', 'Virtual Machine Licenses') and (x_SkuMeterSubcategory contains 'Windows' or coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL'), 'Windows Server',\n isnotempty(tmp_SqlAhb) or x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\n ''\n )\n | extend x_SkuLicenseStatus = case(\n isempty(x_SkuLicenseType), '',\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL' or tmp_SqlAhb == 'true' or x_SkuMeterSubcategory contains 'Azure Hybrid Benefit', 'Enabled',\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not Enabled',\n ''\n )\n | extend x_SkuLicenseQuantity = case(\n isempty(x_SkuCoreCount) or isempty(x_SkuLicenseType), int(null),\n x_SkuCoreCount <= 8, int(8),\n x_SkuCoreCount > 8, x_SkuCoreCount,\n int(null)\n )\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\n //\n // Savings\n | extend x_CommitmentDiscountSavings = iff(isempty(ContractedCost) or ContractedCost == 0 or ContractedCost - EffectiveCost < 0.0001, real(0), ContractedCost - EffectiveCost)\n | extend x_NegotiatedDiscountSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - ContractedCost < 0.0001, real(0), ListCost - ContractedCost)\n | extend x_TotalSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - EffectiveCost < 0.0001, real(0), ListCost - EffectiveCost)\n | extend x_CommitmentDiscountPercent = iff(isempty(ContractedUnitPrice) or ContractedUnitPrice == 0 or ContractedUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\n | extend x_NegotiatedDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - ContractedUnitPrice < 0.0001, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\n | extend x_TotalDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\n //\n // Minor fixes\n | extend old_BillingPeriodEnd = BillingPeriodEnd, BillingPeriodEnd = startofmonth(BillingPeriodEnd)\n | extend old_BillingPeriodStart = BillingPeriodStart, BillingPeriodStart = startofmonth(BillingPeriodStart)\n //\n // Sort columns and apply final transforms\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n CapacityReservationId,\n CapacityReservationStatus,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountQuantity,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\n EffectiveCost,\n InvoiceId = coalesce(InvoiceId, x_InvoiceId),\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory, // TODO: Populate ServiceSubcategory from ServiceName when missing\n SkuId,\n SkuMeter,\n SkuPriceDetails,\n SkuPriceId,\n SubAccountId,\n SubAccountName = iff(isempty(SubAccountId), '', SubAccountName),\n SubAccountType,\n Tags,\n x_AccountId = iff(x_AccountId == '-2', '', x_AccountId),\n x_AccountName = iff(x_AccountId == '-2', '', x_AccountName),\n x_AccountOwnerId = iff(x_AccountId == '-2', '', x_AccountOwnerId),\n x_AmortizationClass,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement = case(\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\n ProviderName\n ),\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingItemCode,\n x_BillingItemName,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountPercent,\n x_CommitmentDiscountSavings,\n x_CommitmentDiscountSpendEligibility = '', // TODO: Add x_CommitmentDiscountSpendEligibility for Costs\n x_CommitmentDiscountUsageEligibility = '', // TODO: Add x_CommitmentDiscountUsageEligibility for Costs\n x_CommitmentDiscountUtilizationAmount,\n x_CommitmentDiscountUtilizationPotential,\n x_CommodityCode,\n x_CommodityName,\n x_ComponentName,\n x_ComponentType,\n x_ConsumedCoreHours,\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd),\n x_CostAllocationRuleName,\n x_CostCategories = parse_json(x_CostCategories),\n x_CostCenter,\n x_CostType,\n x_Credits = parse_json(x_Credits),\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount = parse_json(x_Discount),\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InstanceID,\n x_InvoiceIssuerId,\n x_InvoiceSectionId = case(\n x_InvoiceSectionId == '-2', '',\n x_InvoiceSectionId\n ),\n x_InvoiceSectionName = case(\n x_InvoiceSectionName == 'Unassigned', '',\n x_InvoiceSectionName\n ),\n x_ListCostInUsd,\n x_Location,\n x_NegotiatedDiscountPercent,\n x_NegotiatedDiscountSavings,\n x_Operation,\n x_OwnerAccountID,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription = iff(x_PricingUnitDescription == 'Unassigned', '', x_PricingUnitDescription),\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName = tolower(x_ResourceGroupName),\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServiceModel, // TODO: Populate from ServiceName when missing\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuCoreCount,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuInstanceType,\n x_SkuIsCreditEligible,\n x_SkuLicenseQuantity,\n x_SkuLicenseStatus,\n x_SkuLicenseType,\n x_SkuLicenseUnit,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOperatingSystem,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuPlanName,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceValues = bag_merge(\n checkString('BillingPeriodEnd', old_BillingPeriodEnd, BillingPeriodEnd),\n checkString('BillingPeriodStart', old_BillingPeriodStart, BillingPeriodStart),\n checkString('CapacityReservationStatus', old_CapacityReservationStatus, CapacityReservationStatus),\n checkString('ChargeCategory', old_ChargeCategory, ChargeCategory),\n checkString('ChargeClass', old_ChargeClass, ChargeClass),\n checkString('ChargeSubcategory', old_ChargeSubcategory, ''), // Not included in final schema; use empty string\n checkString('ChargeFrequency', old_ChargeFrequency, ChargeFrequency),\n checkReal('CommitmentDiscountQuantity', old_CommitmentDiscountQuantity, CommitmentDiscountQuantity),\n checkString('CommitmentDiscountUnit', old_CommitmentDiscountUnit, CommitmentDiscountUnit),\n checkString('CommitmentDiscountStatus', old_CommitmentDiscountStatus, CommitmentDiscountStatus),\n checkReal('ConsumedQuantity', old_ConsumedQuantity, ConsumedQuantity),\n checkString('ConsumedUnit', old_ConsumedUnit, ConsumedUnit),\n checkReal('ContractedCost', old_ContractedCost, ContractedCost),\n checkReal('ContractedUnitPrice', old_ContractedUnitPrice, ContractedUnitPrice),\n checkReal('EffectiveCost', old_EffectiveCost, EffectiveCost),\n checkReal('ListCost', old_ListCost, ListCost),\n checkReal('ListUnitPrice', old_ListUnitPrice, ListUnitPrice),\n checkString('PricingCategory', old_PricingCategory, PricingCategory),\n checkReal('PricingQuantity', old_PricingQuantity, PricingQuantity),\n checkString('ProviderName', old_ProviderName, ProviderName),\n checkString('PublisherName', old_PublisherName, PublisherName),\n checkString('Region', old_Region, ''), // Not included in final schema; use empty string\n checkString('RegionId', old_RegionId, RegionId),\n checkString('ResourceId', old_ResourceId, ResourceId),\n checkString('ResourceName', old_ResourceName, ResourceName),\n checkString('ResourceType', old_ResourceType, ResourceType),\n checkString('x_AmortizationClass', old_x_AmortizationClass, x_AmortizationClass),\n checkReal('x_EffectiveCostInUsd', old_x_EffectiveCostInUsd, x_EffectiveCostInUsd),\n checkReal('x_EffectiveUnitPrice', old_x_EffectiveUnitPrice, x_EffectiveUnitPrice),\n checkString('x_ResourceType', old_x_ResourceType, x_ResourceType)\n ),\n x_SourceVersion,\n x_SubproductName,\n x_TotalDiscountPercent,\n x_TotalSavings,\n x_UsageType\n}\n\n// Costs_final_v1_2 table\n.create-merge table Costs_final_v1_2 (\n AvailabilityZone: string,\n BilledCost: real,\n BillingAccountId: string,\n BillingAccountName: string,\n BillingAccountType: string,\n BillingCurrency: string,\n BillingPeriodEnd: datetime,\n BillingPeriodStart: datetime,\n CapacityReservationId: string,\n CapacityReservationStatus: string,\n ChargeCategory: string,\n ChargeClass: string,\n ChargeDescription: string,\n ChargeFrequency: string,\n ChargePeriodEnd: datetime,\n ChargePeriodStart: datetime,\n CommitmentDiscountCategory: string,\n CommitmentDiscountId: string,\n CommitmentDiscountName: string,\n CommitmentDiscountQuantity: real,\n CommitmentDiscountStatus: string,\n CommitmentDiscountType: string,\n CommitmentDiscountUnit: string,\n ConsumedQuantity: real,\n ConsumedUnit: string,\n ContractedCost: real,\n ContractedUnitPrice: real,\n EffectiveCost: real,\n InvoiceId: string,\n InvoiceIssuerName: string,\n ListCost: real,\n ListUnitPrice: real,\n PricingCategory: string,\n PricingCurrency: string,\n PricingQuantity: real,\n PricingUnit: string,\n ProviderName: string,\n PublisherName: string,\n RegionId: string,\n RegionName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n ServiceCategory: string,\n ServiceName: string,\n ServiceSubcategory: string,\n SkuId: string,\n SkuMeter: string,\n SkuPriceDetails: dynamic,\n SkuPriceId: string,\n SubAccountId: string,\n SubAccountName: string,\n SubAccountType: string,\n Tags: dynamic,\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_AmortizationClass: string, // Azure 1.2-preview+\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingItemCode: string, // Alibaba 1.0\n x_BillingItemName: string, // Alibaba 1.0\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_CommitmentDiscountNormalizedRatio: real, // Azure 1.2-preview+\n x_CommitmentDiscountPercent: real, // Hubs add-on\n x_CommitmentDiscountSavings: real, // Hubs add-on\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_CommitmentDiscountUtilizationAmount: real, // Hubs add-on\n x_CommitmentDiscountUtilizationPotential: real, // Hubs add-on\n x_CommodityCode: string, // Alibaba 1.0\n x_CommodityName: string, // Alibaba 1.0\n x_ComponentName: string, // Tencent 1.0\n x_ComponentType: string, // Tencent 1.0\n x_ConsumedCoreHours: real, // Hubs add-on\n x_ContractedCostInUsd: real, // Azure 1.0+\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_CostType: string, // GCP Jan 2024\n x_Credits: dynamic, // GCP Jan 2024\n x_CurrencyConversionRate: real, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: dynamic, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0\n x_IngestionTime: datetime, // Hubs add-on\n x_InstanceID: string, // Alibaba 1.0\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_NegotiatedDiscountPercent:real, // Hubs add-on\n x_NegotiatedDiscountSavings:real, // Hubs add-on\n x_Operation: string, // AWS 1.0\n x_OwnerAccountID: string, // Tencent 1.0\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServiceModel: string, // Azure 1.2-preview+\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuCoreCount: int, // Hubs add-on\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\n x_SkuInstanceType: string, // Hubs add-on\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuLicenseQuantity: int, // Hubs add-on\n x_SkuLicenseStatus: string, // Hubs add-on\n x_SkuLicenseType: string, // Hubs add-on\n x_SkuLicenseUnit: string, // Hubs add-on\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOperatingSystem: string, // Hubs add-on\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuPlanName: string, // Azure 1.2-preview+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceChanges: string, // Hubs add-on\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceValues: dynamic, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubproductName: string, // Tencent 1.0\n x_TotalDiscountPercent: real, // Hubs add-on\n x_TotalSavings: real, // Hubs add-on\n x_UsageType: string // AWS 1.0\n)\n\n// Update policy for Costs_raw -> Costs_final_v1_2 table\n.alter table Costs_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Costs_raw\",\n \"Query\": \"Costs_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Actual costs |===================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_transform_v1_2 function\n.create-or-alter function\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\nActualCosts_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n ActualCosts_raw\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodEnd = Date + 1d,\n ChargePeriodStart = Date,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId = '',\n SkuMeter = MeterName,\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentType = '',\n x_ComponentName = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = '',\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel,\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for ActualCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": true,\n \"Source\": \"ActualCosts_raw\",\n \"Query\": \"ActualCosts_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Amortized costs |================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_transform_v1_2 function\n.create-or-alter function\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\nAmortizedCosts_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n AmortizedCosts_raw\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodEnd = Date + 1d,\n ChargePeriodStart = Date,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId = '',\n SkuMeter = MeterName,\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentType = '',\n x_ComponentName = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = '',\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel,\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for AmortizedCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": true,\n \"Source\": \"AmortizedCosts_raw\",\n \"Query\": \"AmortizedCosts_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All commitment discount usage transformed to FOCUS 1.2. This includes reservationdeatils_raw.', folder='Commitment discounts')\nCommitmentDiscountUsage_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n CommitmentDiscountUsage_raw\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Handle resource columns\n | extend ResourceId = tolower(InstanceId)\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n //\n // Sort columns and apply final transforms\n | project\n ChargePeriodEnd = UsageDate + 1d,\n ChargePeriodStart = UsageDate,\n CommitmentDiscountCategory = 'Usage',\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\n CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\n CommitmentDiscountType = 'Reservation',\n CommitmentDiscountUnit = case(\n InstanceFlexibilityRatio == 1, 'Hours',\n InstanceFlexibilityRatio != 1, 'Normalized Hours',\n ''\n ),\n ConsumedQuantity = UsedHours,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SubAccountId,\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\n x_CommitmentDiscountCommittedAmount = ReservedHours,\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\n x_IngestionTime = ingestion_time(),\n x_ResourceGroupName,\n x_ResourceType,\n // x_RowId = hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // CommitmentDiscountId,\n // ResourceId,\n // ChargePeriodStart\n // )),\n x_ServiceModel,\n x_SkuOrderId = ReservationOrderId,\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\n}\n\n// CommitmentDiscountUsage_final_v1_2 table\n.create-merge table CommitmentDiscountUsage_final_v1_2 (\n ChargePeriodEnd: datetime, // Hubs add-on\n ChargePeriodStart: datetime, // MS 2023-03-01\n CommitmentDiscountCategory: string, // Hubs add-on\n CommitmentDiscountId: string, // MS 2023-03-01\n CommitmentDiscountQuantity: real, // MS 2023-03-01\n CommitmentDiscountType: string, // Hubs add-on\n CommitmentDiscountUnit: string, // Hubs add-on\n ConsumedQuantity: real, // MS 2023-03-01\n ProviderName: string, // Hubs add-on\n ResourceId: string, // MS 2023-03-01\n ResourceName: string, // Hubs add-on\n ResourceType: string, // Hubs add-on\n ServiceCategory: string, // Hubs add-on\n ServiceName: string, // Hubs add-on\n ServiceSubcategory: string, // Hubs add-on\n SubAccountId: string, // Hubs add-on\n x_CommitmentDiscountCommittedCount: real, // MS 2023-03-01\n x_CommitmentDiscountCommittedAmount: real, // MS 2023-03-01\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\n x_CommitmentDiscountNormalizedRatio: real, // MS 2023-03-01\n x_IngestionTime: datetime, // Hubs add-on\n x_ResourceGroupName: string, // Hubs add-on\n x_ResourceType: string, // Hubs add-on\n x_ServiceModel: string, // Hubs add-on\n x_SkuOrderId: string, // MS 2023-03-01\n x_SkuSize: string, // MS 2023-03-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string // Hubs add-on\n)\n\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_2 table\n.alter table CommitmentDiscountUsage_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"CommitmentDiscountUsage_raw\",\n \"Query\": \"CommitmentDiscountUsage_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All recommendations transformed to FOCUS 1.2.', folder='Recommendations')\nRecommendations_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Recommendations_raw\n | extend x_IngestionTime = ingestion_time()\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Convert JSON cost columns to real\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\n //\n // Build recommendation details\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\n | extend x_RecommendationDetails = case(\n // Use incoming x_RecommendationDetails first\n isnotempty(x_RecommendationDetails), x_RecommendationDetails,\n // Create one for reservation recommendations if needed\n x_SourceType == 'ReservationRecommendations', bag_pack(\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\n 'CommitmentDiscountResourceType', ResourceType,\n 'CommitmentDiscountScope', Scope,\n 'LookbackPeriodDuration', case(\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\n ''\n ),\n 'LookbackPeriodStart', FirstUsageDate,\n 'RecommendedQuantity', RecommendedQuantity,\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\n 'RegionId', Location,\n 'RegionName', RegionName,\n 'SkuMeterId', MeterId,\n 'SkuPriceDetails', SkuProperties,\n 'SkuSize', coalesce(SKU, SkuName),\n 'SkuTerm', isoMonths(Term)\n ),\n dynamic({})\n )\n //\n // Prefer specified date, then fall back to generating a date based on reservation recommendation lookback period, then validate to ensure it's not in the future\n | extend x_RecommendationDate = coalesce(x_RecommendationDate, FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d))\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\n //\n | project\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n SubAccountId = coalesce(SubAccountId, iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), '')),\n SubAccountName,\n x_EffectiveCostAfter = coalesce(x_EffectiveCostAfter, TotalCostWithReservedInstances),\n x_EffectiveCostBefore = coalesce(x_EffectiveCostBefore, CostWithNoReservedInstances),\n x_EffectiveCostSavings = coalesce(x_EffectiveCostSavings, NetSavings),\n x_IngestionTime,\n x_RecommendationCategory, // TODO: Set for reservation recommendations\n x_RecommendationDate,\n x_RecommendationDescription,\n x_RecommendationDetails,\n x_RecommendationId, // TODO: Set for reservation recommendations\n x_ResourceGroupName,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n// Recommendations_final_v1_2 table\n.create-merge table Recommendations_final_v1_2 (\n ProviderName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n SubAccountId: string,\n SubAccountName: string,\n x_EffectiveCostAfter: real,\n x_EffectiveCostBefore: real,\n x_EffectiveCostSavings: real,\n x_IngestionTime: datetime,\n x_RecommendationCategory: string,\n x_RecommendationDate: datetime,\n x_RecommendationDescription: string,\n x_RecommendationDetails: dynamic,\n x_RecommendationId: string,\n x_ResourceGroupName: string,\n x_SourceName: string,\n x_SourceProvider: string,\n x_SourceType: string,\n x_SourceVersion: string\n)\n\n// Update policy for Recommendations_raw -> Recommendations_final_v1_2 table\n.alter table Recommendations_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Recommendations_raw\",\n \"Query\": \"Recommendations_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_transform_v1_2 function\n.create-or-alter function\nwith (docstring='All transactions transformed to FOCUS 1.2.', folder='Transactions')\nTransactions_transform_v1_2()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', int(null),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Transactions_raw\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Handle BillingPeriodStart/End\n | extend BillingMonth = tostring(BillingMonth)\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\n //\n // Sort columns and apply final transforms\n | project\n BilledCost = Amount,\n BillingAccountId = case(\n BillingProfileId startswith '/', BillingProfileId,\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\n ''\n ),\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\n BillingCurrency = Currency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory = case(\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = case(\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\n EventType == 'Refund', 'Correction',\n ''\n ),\n ChargeDescription = Description,\n ChargeFrequency = case(\n BillingFrequency == 'OneTime', 'One-Time',\n BillingFrequency == 'Recurring', 'Recurring',\n BillingFrequency\n ),\n ChargePeriodStart = EventDate,\n InvoiceId,\n PricingQuantity = Quantity,\n PricingUnit = 'Reservations',\n ProviderName,\n RegionId = Region,\n RegionName = Region,\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerEmail,\n x_CostCenter = CostCenter,\n x_InvoiceNumber = Invoice,\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\n x_IngestionTime = ingestion_time(),\n x_MonetaryCommitment = MonetaryCommitment,\n x_Overage = Overage,\n x_PurchasingBillingAccountId = PurchasingEnrollment,\n x_SkuOrderId = ReservationOrderId,\n x_SkuOrderName = ReservationOrderName,\n x_SkuSize = ArmSkuName,\n x_SkuTerm = isoMonths(Term),\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId = PurchasingSubscriptionGuid,\n x_TransactionType = EventType\n}\n\n// Transactions_final_v1_2 table\n.create-merge table Transactions_final_v1_2 (\n BilledCost: real, // MS CM EA+MCA 2023-05-01\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n ChargeCategory: string, // Hubs add-on\n ChargeClass: string, // Hubs add-on\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n InvoiceId: string, // MS CM MCA 2023-05-01\n PricingQuantity: real, // MS CM EA+MCA 2023-05-01\n PricingUnit: string, // Hubs add-on\n ProviderName: string, // Hubs add-on\n RegionId: string, // MS CM EA+MCA 2023-05-01\n RegionName: string, // MS CM EA+MCA 2023-05-01\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\n x_AccountName: string, // MS CM EA 2023-05-01\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\n x_CostCenter: string, // MS CM EA 2023-05-01\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\n x_IngestionTime: datetime, // Hubs add-on\n x_MonetaryCommitment: real, // MS CM EA 2023-05-01\n x_Overage: real, // MS CM EA 2023-05-01\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\n)\n\n// Update policy for Transactions_raw -> Transactions_final_v1_2 table\n.alter table Transactions_final_v1_2 policy update\n```\n[{\n \"IsEnabled\": true,\n \"Source\": \"Transactions_raw\",\n \"Query\": \"Transactions_transform_v1_2()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n", - "$fxv#11": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Common utility functions\n//\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\n//======================================================================================================================\n\n\n//===| Date functions |=================================================================================================\n\n// monthstring\n.create-or-alter function \nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \nmonthstring(['date']: datetime, length: int = 9)\n{\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\n}\n\n// datestring\n.create-or-alter function \nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n let month = (d: datetime) { monthstring(d, 3) };\n let endDate = iff(end == datetime('0001-01-01'), start, end);\n let sameDate = startofday(start) == startofday(endDate);\n let sameMonth = startofmonth(start) == startofmonth(endDate);\n let sameYear = startofyear(start) == startofyear(endDate);\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\n let currentYear = sameYear and startofyear(start) == startofyear(now());\n case(\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\n fullYear,\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\n // 1 full mo, same year | Mmm yyyy\n fullMonth and sameMonth and sameYear,\n strcat(month(start), ' ', getyear(start)),\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\n fullMonth and sameYear,\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\n fullMonth and not(sameYear),\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\n sameDate,\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\n not(fullMonth) and sameMonth and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\n not(fullMonth) and not(sameMonth) and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\n )\n}\n\n// daterange\n.create-or-alter function \nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n datestring(start, end)\n}\n\n// monthsago\n.create-or-alter function \nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\nmonthsago(months: int)\n{\n datetime_add('month', -months, startofmonth(now()))\n}\n\n\n//===| Number functions |===============================================================================================\n// NOTE: Must be defined before string converters\n\n// delta\n.create-or-alter function \nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \ndelta(oldval: double, newval: double)\n{\n (newval - todouble(oldval))/oldval\n}\n\n// percentOfTotal\n// NOTE: Must be before percent() function\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercentOfTotal(t: (Count: long), tot: long)\n{\n let total = todouble(tot);\n t \n | extend Percent = round(Count / total * 100, 3) \n | order by Count desc\n}\n\n// percent\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercent(t: (Count: long))\n{\n let total = todouble(toscalar(t | summarize sum(Count)));\n percentOfTotal(t, total)\n}\n\n// plusminus\n.create-or-alter function \nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\nplusminus(val: string)\n{\n let neg = substring(val, 0, 1) == '-';\n iff(neg, val, strcat('+', val))\n}\n\n// updown\n.create-or-alter function \nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\nupdown(val: string)\n{\n // TODO: Handle 0\n let neg = substring(val, 0, 1) == '-';\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\n}\n\n\n//===| String functions |===============================================================================================\n\n// percentstring\n// NOTE: Must be defined before deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\npercentstring(num: double, total: double = 1.0, places: int = 9)\n{\n let value = 1.0 * num / total * 100;\n strcat(case(\n places != 9, round(value, places),\n value < 10, round(value, 2),\n round(value, 1)\n ), '%')\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// arraystring\n.create-or-alter function \nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\narraystring(arr: dynamic)\n{\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\n tostring(arr)\n , @'^\\[\"', '')\n , @'\"\\]$', '')\n , @'^, ', '')\n , @', $', '')\n , @'^\\[]$', '')\n , '\",\"', ', ')\n}\n\n// deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\n{\n let d = delta(oldval, newval);\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\n}\n\n// diffstring\n.create-or-alter function \nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\ndiffstring(oldval: double, newval: double, places: int = 1)\n{\n plusminus(round(newval - oldval, places))\n}\n\n// numberstring\n.create-or-alter function \nwith (docstring = 'Convert a number to a string', folder = 'Common')\nnumberstring(num: double, abbrev: bool = true)\n{\n replace_regex(case(\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\n tostring(num)\n ), @'\\.0$', '')\n}\n\n\n//===| Other |==========================================================================================================\n\n// ifempty\n.create-or-alter function \nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\nifempty(val: dynamic, defaultVal: dynamic)\n{\n iff(isempty(val), defaultVal, val)\n}\n", - "$fxv#12": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / Open data functions\n// Wrap Ingestion database tables for easy access.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// PricingUnits\n.create-or-alter function\nwith (docstring = 'Gets pricing units from the FinOps toolkit PricingUnits open data.', folder = 'OpenData')\nPricingUnits()\n{\n database('Ingestion').PricingUnits\n}\n\n// Regions\n.create-or-alter function\nwith (docstring = 'Gets regions from the FinOps toolkit Regions open data.', folder = 'OpenData')\nRegion()\n{\n database('Ingestion').Regions\n}\n\n// ResourceTypes\n.create-or-alter function\nwith (docstring = 'Gets resource types from the FinOps toolkit ResourceTypes open data.', folder = 'OpenData')\nResourceType()\n{\n database('Ingestion').ResourceTypes\n}\n\n// Services\n.create-or-alter function\nwith (docstring = 'Gets services from the FinOps toolkit Services open data.', folder = 'OpenData')\nServices()\n{\n database('Ingestion').Services\n}\n", - "$fxv#13": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / FOCUS 1.0 functions\n// Used for reporting with backward compatibility.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// CommitmentDiscountUsage_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.0.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage_v1_0()\n{\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\n | union (\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\n // Convert real to decimal\n | extend\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n x_CommitmentDiscountCommittedCount = todecimal(x_CommitmentDiscountCommittedCount),\n x_CommitmentDiscountCommittedAmount = todecimal(x_CommitmentDiscountCommittedAmount),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio)\n )\n | project\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountType,\n ConsumedQuantity,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SubAccountId,\n x_CommitmentDiscountCommittedCount,\n x_CommitmentDiscountCommittedAmount,\n x_CommitmentDiscountNormalizedGroup,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountQuantity,\n x_IngestionTime,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceModel,\n x_SkuOrderId,\n x_SkuSize,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Costs_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.0.', folder = 'Costs')\nCosts_v1_0()\n{\n database('Ingestion').Costs_final_v1_0\n | union (\n database('Ingestion').Costs_final_v1_2\n // Convert real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n ContractedCost = todecimal(ContractedCost),\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n EffectiveCost = todecimal(EffectiveCost),\n ListCost = todecimal(ListCost),\n ListUnitPrice = todecimal(ListUnitPrice),\n PricingQuantity = todecimal(PricingQuantity),\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\n // Rename columns\n | project-rename\n x_InvoiceId = InvoiceId,\n x_PricingCurrency = PricingCurrency,\n x_SkuMeterName = SkuMeter\n // Generate historical x_SkuDetails format from SkuPriceDetails\n | extend x_SkuDetails = iff(isnotempty(x_SkuDetails), x_SkuDetails, parse_json(replace_regex(tostring(SkuPriceDetails), @'([\\{,])\"x_', @'\\1\"')))\n )\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost,\n ContractedUnitPrice,\n EffectiveCost,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SkuId,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType,\n Tags,\n x_AccountId,\n x_AccountName,\n x_AccountOwnerId,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_ContractedCostInUsd,\n x_CostAllocationRuleName,\n x_CostCategories,\n x_CostCenter,\n x_Credits,\n x_CostType,\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount,\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InvoiceId,\n x_InvoiceIssuerId,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_ListCostInUsd,\n x_Location,\n x_Operation,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingCurrency,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuIsCreditEligible,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_UsageType\n}\n\n\n// Prices_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all prices aligned to FOCUS 1.0.', folder = 'Prices')\nPrices_v1_0()\n{\n database('Ingestion').Prices_final_v1_0\n | union (\n database('Ingestion').Prices_final_v1_2\n // Convert real to decimal\n | extend\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n ListUnitPrice = todecimal(ListUnitPrice),\n x_BaseUnitPrice = todecimal(x_BaseUnitPrice),\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\n x_ContractedUnitPriceDiscount = todecimal(x_ContractedUnitPriceDiscount),\n x_ContractedUnitPriceDiscountPercent = todecimal(x_ContractedUnitPriceDiscountPercent),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_EffectiveUnitPriceDiscount = todecimal(x_EffectiveUnitPriceDiscount),\n x_EffectiveUnitPriceDiscountPercent = todecimal(x_EffectiveUnitPriceDiscountPercent),\n x_PricingBlockSize = todecimal(x_PricingBlockSize),\n x_SkuIncludedQuantity = todecimal(x_SkuIncludedQuantity),\n x_SkuTier = todecimal(x_SkuTier),\n x_TotalUnitPriceDiscount = todecimal(x_TotalUnitPriceDiscount),\n x_TotalUnitPriceDiscountPercent = todecimal(x_TotalUnitPriceDiscountPercent) \n // Rename columns\n | project-rename\n x_PricingCurrency = PricingCurrency,\n x_SkuMeterName = SkuMeter\n )\n | project\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType,\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory,\n PricingUnit,\n SkuId,\n SkuPriceId,\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent,\n x_EffectivePeriodEnd,\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingCurrency,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_SkuDescription,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent\n}\n\n\n// Recommendations_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.0.', folder = 'Recommendations')\nRecommendations_v1_0()\n{\n database('Ingestion').Recommendations_final_v1_0\n | union (\n database('Ingestion').Recommendations_final_v1_2\n // Convert real to decimal\n | extend\n x_EffectiveCostAfter = todecimal(x_EffectiveCostAfter),\n x_EffectiveCostBefore = todecimal(x_EffectiveCostBefore),\n x_EffectiveCostSavings = todecimal(x_EffectiveCostSavings)\n )\n | project\n ProviderName,\n SubAccountId,\n x_IngestionTime,\n x_EffectiveCostAfter,\n x_EffectiveCostBefore,\n x_EffectiveCostSavings,\n x_RecommendationDate,\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Transactions_final_v1_0\n.create-or-alter function\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.0.', folder = 'Transactions')\nTransactions_v1_0()\n{\n database('Ingestion').Transactions_final_v1_0\n | union (\n database('Ingestion').Transactions_final_v1_2\n // Convert real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n PricingQuantity = todecimal(PricingQuantity),\n x_MonetaryCommitment = todecimal(x_MonetaryCommitment),\n x_Overage = todecimal(x_Overage)\n // Rename columns\n | project-rename\n x_InvoiceId = InvoiceId\n )\n | project\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodStart,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n RegionId,\n RegionName,\n SubAccountId,\n SubAccountName,\n x_AccountName,\n x_AccountOwnerId,\n x_CostCenter,\n x_InvoiceId,\n x_InvoiceNumber,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_IngestionTime,\n x_MonetaryCommitment,\n x_Overage,\n x_PurchasingBillingAccountId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuSize,\n x_SkuTerm,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId,\n x_TransactionType\n}\n", - "$fxv#14": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / FOCUS 1.2 functions\n// Used for reporting with backward compatibility.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n// CommitmentDiscountUsage_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.2.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage_v1_2()\n{\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\n | union (\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\n // Convert decimal to real\n | extend\n ConsumedQuantity = toreal(ConsumedQuantity),\n x_CommitmentDiscountCommittedCount = toreal(x_CommitmentDiscountCommittedCount),\n x_CommitmentDiscountCommittedAmount = toreal(x_CommitmentDiscountCommittedAmount),\n x_CommitmentDiscountNormalizedRatio = toreal(x_CommitmentDiscountNormalizedRatio)\n // Add new columns\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceSubcategory) on x_ResourceType\n | extend CommitmentDiscountQuantity = ConsumedQuantity * x_CommitmentDiscountNormalizedRatio\n | extend CommitmentDiscountUnit = case(\n x_CommitmentDiscountNormalizedRatio == 1, 'Hours',\n x_CommitmentDiscountNormalizedRatio > 1, 'Normalized Hours',\n ''\n )\n )\n | project\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountQuantity,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SubAccountId,\n x_CommitmentDiscountCommittedCount,\n x_CommitmentDiscountCommittedAmount,\n x_CommitmentDiscountNormalizedGroup,\n x_CommitmentDiscountNormalizedRatio,\n x_IngestionTime,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceModel,\n x_SkuOrderId,\n x_SkuSize,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Costs_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.2.', folder = 'Costs')\nCosts_v1_2()\n{\n database('Ingestion').Costs_final_v1_2\n | union (\n database('Ingestion').Costs_final_v1_0\n // Convert decimal to real\n | extend\n BilledCost = toreal(BilledCost),\n ConsumedQuantity = toreal(ConsumedQuantity),\n ContractedCost = toreal(ContractedCost),\n ContractedUnitPrice = toreal(ContractedUnitPrice),\n EffectiveCost = toreal(EffectiveCost),\n ListCost = toreal(ListCost),\n ListUnitPrice = toreal(ListUnitPrice),\n PricingQuantity = toreal(PricingQuantity),\n x_BilledCostInUsd = toreal(x_BilledCostInUsd),\n x_BilledUnitPrice = toreal(x_BilledUnitPrice),\n x_BillingExchangeRate = toreal(x_BillingExchangeRate),\n x_ContractedCostInUsd = toreal(x_ContractedCostInUsd),\n x_CurrencyConversionRate = toreal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = toreal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\n x_ListCostInUsd = toreal(x_ListCostInUsd),\n x_PricingBlockSize = toreal(x_PricingBlockSize)\n // Rename columns\n | project-rename\n InvoiceId = x_InvoiceId,\n PricingCurrency = x_PricingCurrency,\n SkuMeter = x_SkuMeterName\n // Add new columns\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceSubcategory, x_ServiceModel) on x_ResourceType\n | extend CapacityReservationId = tostring(x_SkuDetails.VMCapacityReservationId)\n | extend CapacityReservationStatus = case(\n isempty(CapacityReservationId), '',\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\n 'Used'\n )\n | extend x_CommitmentDiscountNormalizedRatio = case(\n // Not applicable\n isempty(CommitmentDiscountStatus), real(null),\n // Parse from SKU details if not specified explicitly\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, dynamic(1)))\n )\n | extend CommitmentDiscountQuantity = case(\n isempty(CommitmentDiscountStatus), real(null),\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\n real(null)\n )\n | extend CommitmentDiscountUnit = case(\n isempty(CommitmentDiscountQuantity), '',\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\n ''\n )\n | extend x_AmortizationClass = case(\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\n ''\n )\n // Hubs add-ons\n | extend x_CommitmentDiscountUtilizationPotential = case(\n ChargeCategory == 'Purchase', real(0),\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\n real(0)\n )\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\n | extend x_SkuCoreCount = toint(coalesce(x_SkuDetails.VCPUs, x_SkuDetails.VCores, x_SkuDetails.vCores))\n | extend x_SkuInstanceType = tostring(coalesce(x_SkuDetails.ServiceType, x_SkuDetails.ServerSku))\n | extend x_SkuOperatingSystem = case(\n x_SkuDetails.ImageType == 'Canonical', 'Linux',\n x_SkuDetails.ImageType == 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\n x_SkuDetails.ImageType\n )\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\n | extend tmp_SqlAhb = tolower(x_SkuDetails.AHB)\n | extend x_SkuLicenseType = case(\n x_SkuDetails.ImageType contains 'Windows Server BYOL', 'Windows Server',\n x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\n ''\n )\n | extend x_SkuLicenseStatus = case(\n isnotempty(x_SkuLicenseType) or tmp_SqlAhb == 'true' or (x_SkuMeterSubcategory contains 'Azure Hybrid Benefit'), 'Enabled',\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not enabled',\n ''\n )\n | extend x_SkuLicenseQuantity = case(\n isempty(x_SkuCoreCount), int(null),\n x_SkuCoreCount <= 8, int(8),\n x_SkuCoreCount > 8, x_SkuCoreCount,\n int(null)\n )\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\n | extend x_CommitmentDiscountSavings = iff(ContractedCost < EffectiveCost, real(0), ContractedCost - EffectiveCost)\n | extend x_NegotiatedDiscountSavings = iff(ListCost < ContractedCost, real(0), ListCost - ContractedCost)\n | extend x_TotalSavings = iff(ListCost < EffectiveCost, real(0), ListCost - EffectiveCost)\n | extend x_CommitmentDiscountPercent = iff(ContractedUnitPrice == 0, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\n | extend x_NegotiatedDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\n | extend x_TotalDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\n // SkuPriceDetails conversion -- Must be after hubs add-ons\n | extend SkuPriceDetails = parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\n // Prefix all keys with x_ first to avoid double-prefixing\n , @'([\\{,])\"', @'\\1\"x_')\n // CoreCount for number of CPUs/vCPUs/cores/vCores\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\n // TODO: DiskSpace for disk size in GiB\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\n // TODO: GpuCount for the number of GPUs\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\n // TODO: InstanceSeries for the size family/series\n // TODO: MemorySize for the RAM in GiB\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\n // OperatingSystem for the OS name\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\n )\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\n SkuPriceDetails)\n )\n | extend SkuPriceDetails = iff(isnotempty(SkuPriceDetails), SkuPriceDetails, parse_json(replace_regex(tostring(x_SkuDetails), @'([\\{,])\"', @'\\1\"x_')))\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n CapacityReservationId,\n CapacityReservationStatus,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId,\n CommitmentDiscountName,\n CommitmentDiscountQuantity,\n CommitmentDiscountStatus,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost,\n ContractedUnitPrice,\n EffectiveCost,\n InvoiceId,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n PublisherName,\n RegionId,\n RegionName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory,\n SkuId,\n SkuMeter,\n SkuPriceDetails,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType,\n Tags,\n x_AccountId,\n x_AccountName,\n x_AccountOwnerId,\n x_AmortizationClass,\n x_BilledCostInUsd,\n x_BilledUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingAccountName,\n x_BillingExchangeRate,\n x_BillingExchangeRateDate,\n x_BillingItemCode,\n x_BillingItemName,\n x_BillingProfileId,\n x_BillingProfileName,\n x_ChargeId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountPercent,\n x_CommitmentDiscountSavings,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_CommitmentDiscountUtilizationAmount,\n x_CommitmentDiscountUtilizationPotential,\n x_CommodityCode,\n x_CommodityName,\n x_ComponentName,\n x_ComponentType,\n x_ConsumedCoreHours,\n x_ContractedCostInUsd,\n x_CostAllocationRuleName,\n x_CostCategories,\n x_CostCenter,\n x_CostType,\n x_Credits,\n x_CurrencyConversionRate,\n x_CustomerId,\n x_CustomerName,\n x_Discount,\n x_EffectiveCostInUsd,\n x_EffectiveUnitPrice,\n x_ExportTime,\n x_IngestionTime,\n x_InstanceID,\n x_InvoiceIssuerId,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_ListCostInUsd,\n x_Location,\n x_NegotiatedDiscountPercent,\n x_NegotiatedDiscountSavings,\n x_Operation,\n x_OwnerAccountID,\n x_PartnerCreditApplied,\n x_PartnerCreditRate,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_Project,\n x_PublisherCategory,\n x_PublisherId,\n x_ResellerId,\n x_ResellerName,\n x_ResourceGroupName,\n x_ResourceType,\n x_ServiceCode,\n x_ServiceId,\n x_ServiceModel,\n x_ServicePeriodEnd,\n x_ServicePeriodStart,\n x_SkuCoreCount,\n x_SkuDescription,\n x_SkuDetails,\n x_SkuInstanceType,\n x_SkuIsCreditEligible,\n x_SkuLicenseQuantity,\n x_SkuLicenseStatus,\n x_SkuLicenseType,\n x_SkuLicenseUnit,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuOfferId,\n x_SkuOperatingSystem,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuPartNumber,\n x_SkuPlanName,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuTerm,\n x_SkuTier,\n x_SourceChanges,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceValues,\n x_SourceVersion,\n x_SubproductName,\n x_TotalDiscountPercent,\n x_TotalSavings,\n x_UsageType\n}\n\n\n// Prices_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all prices aligned to FOCUS 1.2.', folder = 'Prices')\nPrices_v1_2()\n{\n database('Ingestion').Prices_final_v1_2\n | union (\n database('Ingestion').Prices_final_v1_0\n // Convert decimal to real\n | extend\n ContractedUnitPrice = toreal(ContractedUnitPrice),\n ListUnitPrice = toreal(ListUnitPrice),\n x_BaseUnitPrice = toreal(x_BaseUnitPrice),\n x_ContractedUnitPriceDiscount = toreal(x_ContractedUnitPriceDiscount),\n x_ContractedUnitPriceDiscountPercent = toreal(x_ContractedUnitPriceDiscountPercent),\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\n x_EffectiveUnitPriceDiscount = toreal(x_EffectiveUnitPriceDiscount),\n x_EffectiveUnitPriceDiscountPercent = toreal(x_EffectiveUnitPriceDiscountPercent),\n x_PricingBlockSize = toreal(x_PricingBlockSize),\n x_SkuIncludedQuantity = toreal(x_SkuIncludedQuantity),\n x_SkuTier = toreal(x_SkuTier),\n x_TotalUnitPriceDiscount = toreal(x_TotalUnitPriceDiscount),\n x_TotalUnitPriceDiscountPercent = toreal(x_TotalUnitPriceDiscountPercent) \n // Rename columns\n | project-rename\n PricingCurrency = x_PricingCurrency,\n SkuMeter = x_SkuMeterName\n )\n | project\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n ChargeCategory,\n CommitmentDiscountCategory,\n CommitmentDiscountType,\n CommitmentDiscountUnit,\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory,\n PricingCurrency,\n PricingUnit,\n SkuId,\n SkuMeter,\n SkuPriceId,\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement,\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountNormalizedRatio,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent,\n x_EffectivePeriodEnd,\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingSubcategory,\n x_PricingUnitDescription,\n x_SkuDescription,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent\n}\n\n\n// Recommendations_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.2.', folder = 'Recommendations')\nRecommendations_v1_2()\n{\n database('Ingestion').Recommendations_final_v1_2\n | union (\n database('Ingestion').Recommendations_final_v1_0\n // Convert decimal to real\n | extend\n x_EffectiveCostAfter = toreal(x_EffectiveCostAfter),\n x_EffectiveCostBefore = toreal(x_EffectiveCostBefore),\n x_EffectiveCostSavings = toreal(x_EffectiveCostSavings)\n )\n | project\n ProviderName,\n SubAccountId,\n x_IngestionTime,\n x_EffectiveCostAfter,\n x_EffectiveCostBefore,\n x_EffectiveCostSavings,\n x_RecommendationDate,\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n\n// Transactions_final_v1_2\n.create-or-alter function\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.2.', folder = 'Transactions')\nTransactions_v1_2()\n{\n database('Ingestion').Transactions_final_v1_2\n | union (\n database('Ingestion').Transactions_final_v1_0\n // Convert decimal to real\n | extend\n BilledCost = toreal(BilledCost),\n PricingQuantity = toreal(PricingQuantity),\n x_MonetaryCommitment = toreal(x_MonetaryCommitment),\n x_Overage = toreal(x_Overage)\n // Rename columns\n | project-rename\n InvoiceId = x_InvoiceId\n )\n | project\n BilledCost,\n BillingAccountId,\n BillingAccountName,\n BillingCurrency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory,\n ChargeClass,\n ChargeDescription,\n ChargeFrequency,\n ChargePeriodStart,\n InvoiceId,\n PricingQuantity,\n PricingUnit,\n ProviderName,\n RegionId,\n RegionName,\n SubAccountId,\n SubAccountName,\n x_AccountName,\n x_AccountOwnerId,\n x_CostCenter,\n x_InvoiceNumber,\n x_InvoiceSectionId,\n x_InvoiceSectionName,\n x_IngestionTime,\n x_MonetaryCommitment,\n x_Overage,\n x_PurchasingBillingAccountId,\n x_SkuOrderId,\n x_SkuOrderName,\n x_SkuSize,\n x_SkuTerm,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId,\n x_TransactionType\n}\n\n\n//======================================================================================================================\n// Latest FOCUS version\n//======================================================================================================================\n\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage()\n{\n CommitmentDiscountUsage_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\nCosts()\n{\n Costs_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\nPrices()\n{\n Prices_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\nRecommendations()\n{\n Recommendations_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\nTransactions()\n{\n Transactions_v1_2()\n}\n", - "$fxv#15": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Hub database / Latest FOCUS version functions\n// Used for ad hoc queries.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n\n.create-or-alter function\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\nCommitmentDiscountUsage()\n{\n CommitmentDiscountUsage_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\nCosts()\n{\n Costs_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\nPrices()\n{\n Prices_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\nRecommendations()\n{\n Recommendations_v1_2()\n}\n\n\n.create-or-alter function\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\nTransactions()\n{\n Transactions_v1_2()\n}\n", - "$fxv#2": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_3(id: string) {\n dynamic({\n \"microsoft.hybridnetwork/vendors\": { \"SingularDisplayName\": \"Azure Network Function Manager ? vendor\" }\n ,\"microsoft.hybridonboarding/extensionmanagers\": { \"SingularDisplayName\": \"Microsoft.HybridOnboarding extension manager\" }\n ,\"microsoft.impact/connectors\": { \"SingularDisplayName\": \"Impact Reporting Connector\" }\n ,\"microsoft.impact/impactcategories\": { \"SingularDisplayName\": \"Microsoft.Impact impact category\" }\n ,\"microsoft.impact/topologyimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact topology impact\" }\n ,\"microsoft.impact/workloadimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact workload impact\" }\n ,\"microsoft.impact/workloadimpacts/insights\": { \"SingularDisplayName\": \"Microsoft.Impact workload impacts insight\" }\n ,\"microsoft.importexport/jobs\": { \"SingularDisplayName\": \"Microsoft.ImportExport job\" }\n ,\"microsoft.insights/actiongroups\": { \"SingularDisplayName\": \"Action group\" }\n ,\"microsoft.insights/activitylogalerts\": { \"SingularDisplayName\": \"Activity log alert rule\" }\n ,\"microsoft.insights/alertrules\": { \"SingularDisplayName\": \"Microsoft.Insights alertrule\" }\n ,\"microsoft.insights/alertrules/incidents\": { \"SingularDisplayName\": \"Microsoft.insights alertrules incident\" }\n ,\"microsoft.insights/autoscalesettings\": { \"SingularDisplayName\": \"Microsoft.Insights autoscalesetting\" }\n ,\"microsoft.insights/components\": { \"SingularDisplayName\": \"Application Insights app\" }\n ,\"microsoft.insights/datacollectionendpoints\": { \"SingularDisplayName\": \"Data collection endpoint\" }\n ,\"microsoft.insights/datacollectionruleassociations\": { \"SingularDisplayName\": \"Microsoft.Insights data collection rule association\" }\n ,\"microsoft.insights/datacollectionrules\": { \"SingularDisplayName\": \"Data collection rule\" }\n ,\"microsoft.insights/datacollectionrulesresources\": { \"SingularDisplayName\": \"Data collection rule associated resource\" }\n ,\"microsoft.insights/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\n ,\"microsoft.insights/diagnosticsettingscategories\": { \"SingularDisplayName\": \"Microsoft.Insights diagnostic settings category\" }\n ,\"microsoft.insights/guestdiagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic setting\" }\n ,\"microsoft.insights/guestdiagnosticsettingsassociation\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic settings association\" }\n ,\"microsoft.insights/logprofiles\": { \"SingularDisplayName\": \"Microsoft.Insights logprofile\" }\n ,\"microsoft.insights/metricalerts\": { \"SingularDisplayName\": \"Metric alert rule\" }\n ,\"microsoft.insights/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights notification statu\" }\n ,\"microsoft.insights/privatelinkscopeoperationstatuses\": { \"SingularDisplayName\": \"Microsoft.insights private link scope operation statuse\" }\n ,\"microsoft.insights/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Monitor Private Link Scope\" }\n ,\"microsoft.insights/scheduledqueryrules\": { \"SingularDisplayName\": \"Log search alert rule\" }\n ,\"microsoft.insights/tenantactiongroups\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action group\" }\n ,\"microsoft.insights/tenantactiongroups/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action groups notification statu\" }\n ,\"microsoft.insights/vminsightsonboardingstatuses\": { \"SingularDisplayName\": \"Microsoft.Insights VM insights onboarding statuse\" }\n ,\"microsoft.insights/webtests\": { \"SingularDisplayName\": \"Application Insights availability test\" }\n ,\"microsoft.insights/workbooks\": { \"SingularDisplayName\": \"Azure Workbook\" }\n ,\"microsoft.insights/workbooktemplates\": { \"SingularDisplayName\": \"Azure Workbook Template\" }\n ,\"microsoft.integrationspaces/spaces\": { \"SingularDisplayName\": \"Integration Environment\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twin\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/assets\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins asset\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/executionplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins execution plan\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/testplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test plan\" }\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/tests\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test\" }\n ,\"microsoft.inventory/subscriptioninternalproperties\": { \"SingularDisplayName\": \"Microsoft.Inventory subscription internal property\" }\n ,\"microsoft.iotcentral/iotapps\": { \"SingularDisplayName\": \"IoT Central Application\" }\n ,\"microsoft.iotfirmwaredefense/workspaces\": { \"SingularDisplayName\": \"Firmware analysis workspace\" }\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmware\" }\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares/summaries\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmwares summary\" }\n ,\"microsoft.iotoperations/instances\": { \"SingularDisplayName\": \"Azure IoT Operations\" }\n ,\"microsoft.iotoperations/instances/brokers\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances broker\" }\n ,\"microsoft.iotoperations/instances/brokers/authentications\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authentication\" }\n ,\"microsoft.iotoperations/instances/brokers/authorizations\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authorization\" }\n ,\"microsoft.iotoperations/instances/brokers/listeners\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers listener\" }\n ,\"microsoft.iotoperations/instances/dataflowendpoints\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow endpoint\" }\n ,\"microsoft.iotoperations/instances/dataflowprofiles\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profile\" }\n ,\"microsoft.iotoperations/instances/dataflowprofiles/dataflows\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profiles dataflow\" }\n ,\"microsoft.iotoperationsdataprocessor/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instance\" }\n ,\"microsoft.iotoperationsdataprocessor/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances dataset\" }\n ,\"microsoft.iotoperationsdataprocessor/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances pipeline\" }\n ,\"microsoft.iotoperationsmq/mq\": { \"SingularDisplayName\": \"IoT Operations Ops MQ\" }\n ,\"microsoft.iotoperationsmq/mq/broker\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker\" }\n ,\"microsoft.iotoperationsmq/mq/broker/authentication\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authentication\" }\n ,\"microsoft.iotoperationsmq/mq/broker/authorization\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authorization\" }\n ,\"microsoft.iotoperationsmq/mq/broker/listener\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker listener\" }\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector\" }\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector topic map\" }\n ,\"microsoft.iotoperationsmq/mq/diagnosticservice\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq diagnostic service\" }\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector\" }\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector topic map\" }\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector\" }\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector topic map\" }\n ,\"microsoft.iotoperationsorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator instance\" }\n ,\"microsoft.iotoperationsorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator solution\" }\n ,\"microsoft.iotoperationsorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator target\" }\n ,\"microsoft.iotsecurity/alerttypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity alert type\" }\n ,\"microsoft.iotsecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity defender setting\" }\n ,\"microsoft.iotsecurity/onpremisesensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity on premise sensor\" }\n ,\"microsoft.iotsecurity/recommendationtypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity recommendation type\" }\n ,\"microsoft.iotsecurity/sensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity sensor\" }\n ,\"microsoft.iotsecurity/sites\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity site\" }\n ,\"microsoft.keyvault/managedhsms\": { \"SingularDisplayName\": \"Azure Key Vault Managed HSM\" }\n ,\"microsoft.keyvault/vaults\": { \"SingularDisplayName\": \"Key vault\" }\n ,\"microsoft.kubernetes/connectedclusters\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc extension\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc namespace\" }\n ,\"microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\n ,\"microsoft.kubernetesconfiguration/extensiontypes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension type\" }\n ,\"microsoft.kubernetesconfiguration/extensiontypes/versions\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension types version\" }\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configuration\" }\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations/operations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configurations operation\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scope\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private endpoint connection\" }\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private link resource\" }\n ,\"microsoft.kubernetesconfiguration/sourcecontrolconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration source control configuration\" }\n ,\"microsoft.kubernetesruntime/bgppeers\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime bgp peer\" }\n ,\"microsoft.kubernetesruntime/loadbalancers\": { \"SingularDisplayName\": \"Arc Load Balancer\" }\n ,\"microsoft.kubernetesruntime/services\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime service\" }\n ,\"microsoft.kubernetesruntime/storageclasses\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime storage class\" }\n ,\"microsoft.kusto/clusters\": { \"SingularDisplayName\": \"Azure Data Explorer Cluster\" }\n ,\"microsoft.kusto/clusters/databases\": { \"SingularDisplayName\": \"Azure Data Explorer Database\" }\n ,\"microsoft.labservices/labaccounts\": { \"SingularDisplayName\": \"Lab account\" }\n ,\"microsoft.labservices/labaccounts/labs\": { \"SingularDisplayName\": \"Lab\" }\n ,\"microsoft.labservices/labplans\": { \"SingularDisplayName\": \"Lab plan\" }\n ,\"microsoft.labservices/labs\": { \"SingularDisplayName\": \"Lab\" }\n ,\"microsoft.liftrpilot/organizations\": { \"SingularDisplayName\": \"Azure Pilot\" }\n ,\"microsoft.loadtestservice/loadtestmappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test mapping\" }\n ,\"microsoft.loadtestservice/loadtestprofilemappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test profile mapping\" }\n ,\"microsoft.loadtestservice/loadtests\": { \"SingularDisplayName\": \"Azure Load Testing\" }\n ,\"microsoft.loadtestservice/playwrightworkspaces\": { \"SingularDisplayName\": \"Playwright Workspace\" }\n ,\"microsoft.logic/businessprocesses\": { \"SingularDisplayName\": \"Business Process\" }\n ,\"microsoft.logic/integrationaccounts\": { \"SingularDisplayName\": \"Logic app integration account\" }\n ,\"microsoft.logic/integrationserviceenvironments\": { \"SingularDisplayName\": \"Integration Service Environment\" }\n ,\"microsoft.logic/integrationserviceenvironments/health\": { \"SingularDisplayName\": \"Microsoft.Logic integration service environments health\" }\n ,\"microsoft.logic/integrationserviceenvironments/managedapis\": { \"SingularDisplayName\": \"Managed Connector\" }\n ,\"microsoft.logic/templates\": { \"SingularDisplayName\": \"Logic Apps Template\" }\n ,\"microsoft.logic/workflows\": { \"SingularDisplayName\": \"Logic app\" }\n ,\"microsoft.logz/monitors\": { \"SingularDisplayName\": \"Logz.io\" }\n ,\"microsoft.logz/monitors/accounts\": { \"SingularDisplayName\": \"Logz sub account\" }\n ,\"microsoft.m365/m365resources\": { \"SingularDisplayName\": \"Microsoft.M365 m365 resource\" }\n ,\"microsoft.m365consumptionservices/services\": { \"SingularDisplayName\": \"Microsoft.M365ConsumptionServices service\" }\n ,\"microsoft.machinelearning/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plan\" }\n ,\"microsoft.machinelearning/commitmentplans/commitmentassociations\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plans commitment association\" }\n ,\"microsoft.machinelearning/webservices\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) web service\" }\n ,\"microsoft.machinelearning/workspaces\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) workspace\" }\n ,\"microsoft.machinelearningexperimentation/accounts\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation account\" }\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspace\" }\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspaces project\" }\n ,\"microsoft.machinelearningservices/aistudio\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.machinelearningservices/aistudiocreate\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\n ,\"microsoft.machinelearningservices/registries\": { \"SingularDisplayName\": \"Azure Machine Learning registry\" }\n ,\"microsoft.machinelearningservices/workspaces\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints\": { \"SingularDisplayName\": \"Machine learning online endpoint\" }\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints/deployments\": { \"SingularDisplayName\": \"Machine learning online deployment\" }\n ,\"microsoft.machinelearningservices/workspacescreate\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\n ,\"microsoft.maintenance/configurationassignments\": { \"SingularDisplayName\": \"Microsoft.Maintenance configuration assignment\" }\n ,\"microsoft.maintenance/maintenanceconfigurations\": { \"SingularDisplayName\": \"Maintenance Configuration\" }\n ,\"microsoft.maintenance/maintenanceconfigurationsaumbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\n ,\"microsoft.maintenance/maintenanceconfigurationsbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\n ,\"microsoft.maintenance/publicmaintenanceconfigurations\": { \"SingularDisplayName\": \"Microsoft.Maintenance public maintenance configuration\" }\n ,\"microsoft.managedidentity/identities\": { \"SingularDisplayName\": \"Microsoft.ManagedIdentity identity\" }\n ,\"microsoft.managedidentity/userassignedidentities\": { \"SingularDisplayName\": \"Managed Identity\" }\n ,\"microsoft.managednetwork/managednetworks\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed network\" }\n ,\"microsoft.managednetwork/managednetworks/managednetworkgroups\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network group\" }\n ,\"microsoft.managednetwork/managednetworks/managednetworkpeeringpolicies\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network peering policy\" }\n ,\"microsoft.managednetworkfabric/accesscontrollists\": { \"SingularDisplayName\": \"Access Control List (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/internetgatewayrules\": { \"SingularDisplayName\": \"Internet Gateway Rule (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/internetgateways\": { \"SingularDisplayName\": \"Internet Gateway (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipcommunities\": { \"SingularDisplayName\": \"IP Community (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipextendedcommunities\": { \"SingularDisplayName\": \"IP Extended Community (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/ipprefixes\": { \"SingularDisplayName\": \"IP Prefix (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l2isolationdomains\": { \"SingularDisplayName\": \"Layer 2 Isolation Domain (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains\": { \"SingularDisplayName\": \"Layer 3 Isolation Domain (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains/externalnetworks\": { \"SingularDisplayName\": \"External Network (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/l3isolationdomains/internalnetworks\": { \"SingularDisplayName\": \"Internal Network (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/neighborgroups\": { \"SingularDisplayName\": \"Neighbor Group (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkdevices\": { \"SingularDisplayName\": \"Network Device (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkdevices/networkinterfaces\": { \"SingularDisplayName\": \"Network Interface (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabriccontrollers\": { \"SingularDisplayName\": \"Network Fabric Controller (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabrics\": { \"SingularDisplayName\": \"Network Fabric (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabrics/networktonetworkinterconnects\": { \"SingularDisplayName\": \"Network to Network Interconnect (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkfabricskus\": { \"SingularDisplayName\": \"Network Fabric SKU (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkmonitors\": { \"SingularDisplayName\": \"Microsoft.ManagedNetworkFabric network monitor\" }\n ,\"microsoft.managednetworkfabric/networkpacketbrokers\": { \"SingularDisplayName\": \"Network Packet Broker (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networkracks\": { \"SingularDisplayName\": \"Network Rack (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networktaprules\": { \"SingularDisplayName\": \"Network Tap Rule (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/networktaps\": { \"SingularDisplayName\": \"Network Tap (Operator Nexus)\" }\n ,\"microsoft.managednetworkfabric/routepolicies\": { \"SingularDisplayName\": \"Route Policy (Operator Nexus)\" }\n ,\"microsoft.managedservices/marketplaceregistrationdefinitions\": { \"SingularDisplayName\": \"Microsoft.ManagedServices marketplace registration definition\" }\n ,\"microsoft.managedservices/registrationassignments\": { \"SingularDisplayName\": \"Microsoft.ManagedServices registration assignment\" }\n ,\"microsoft.managedservices/registrationdefinitions\": { \"SingularDisplayName\": \"Azure Lighthouse\" }\n ,\"microsoft.management/managementgroups\": { \"SingularDisplayName\": \"Microsoft.Management management group\" }\n ,\"microsoft.management/managementgroups/microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\n ,\"microsoft.management/managementgroups/providers/privatelinkassociations\": { \"SingularDisplayName\": \"Application Gateway\" }\n ,\"microsoft.management/managementgroups/providers/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\n ,\"microsoft.management/managementgroups/settings\": { \"SingularDisplayName\": \"Microsoft.Management management groups setting\" }\n ,\"microsoft.management/managementgroups/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Management management groups subscription\" }\n ,\"microsoft.management/servicegroups\": { \"SingularDisplayName\": \"Service group\" }\n ,\"microsoft.managementpartner/partners\": { \"SingularDisplayName\": \"Microsoft.ManagementPartner partner\" }\n ,\"microsoft.manufacturingplatform/manufacturingdataservices\": { \"SingularDisplayName\": \"Factory Operations Agent in Azure AI Foundry\" }\n ,\"microsoft.maps/accounts\": { \"SingularDisplayName\": \"Azure Maps Account\" }\n ,\"microsoft.maps/accounts/creators\": { \"SingularDisplayName\": \"Azure Maps Creator Resource\" }\n ,\"microsoft.marketplace/privatestores\": { \"SingularDisplayName\": \"Microsoft.Marketplace private store\" }\n ,\"microsoft.marketplace/privatestores/adminrequestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores admin request approval\" }\n ,\"microsoft.marketplace/privatestores/collections\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collection\" }\n ,\"microsoft.marketplace/privatestores/collections/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collections offer\" }\n ,\"microsoft.marketplace/privatestores/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores offer\" }\n ,\"microsoft.marketplace/privatestores/requestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores request approval\" }\n ,\"microsoft.media/mediaservices\": { \"SingularDisplayName\": \"Media service\" }\n ,\"microsoft.media/mediaservices/accountfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services account filter\" }\n ,\"microsoft.media/mediaservices/assets\": { \"SingularDisplayName\": \"Microsoft.Media media services asset\" }\n ,\"microsoft.media/mediaservices/assets/assetfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services assets asset filter\" }\n ,\"microsoft.media/mediaservices/assets/tracks\": { \"SingularDisplayName\": \"Microsoft.Media media services assets track\" }\n ,\"microsoft.media/mediaservices/assets/tracks/operationresults\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation result\" }\n ,\"microsoft.media/mediaservices/assets/tracks/operationstatuses\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation statuse\" }\n ,\"microsoft.media/mediaservices/contentkeypolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services content key policy\" }\n ,\"microsoft.media/mediaservices/liveevents\": { \"SingularDisplayName\": \"Live event\" }\n ,\"microsoft.media/mediaservices/liveevents/liveoutputs\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices live events live output\" }\n ,\"microsoft.media/mediaservices/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private endpoint connection\" }\n ,\"microsoft.media/mediaservices/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private link resource\" }\n ,\"microsoft.media/mediaservices/streamingendpoints\": { \"SingularDisplayName\": \"Streaming Endpoint\" }\n ,\"microsoft.media/mediaservices/streaminglocators\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming locator\" }\n ,\"microsoft.media/mediaservices/streamingpolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming policy\" }\n ,\"microsoft.media/mediaservices/transforms\": { \"SingularDisplayName\": \"Microsoft.Media media services transform\" }\n ,\"microsoft.media/mediaservices/transforms/jobs\": { \"SingularDisplayName\": \"Microsoft.Media media services transforms job\" }\n ,\"microsoft.mesh/worlds\": { \"SingularDisplayName\": \"Microsoft.Mesh world\" }\n ,\"microsoft.mesh/worlds/events\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds event\" }\n ,\"microsoft.mesh/worlds/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds events access policy\" }\n ,\"microsoft.mesh/worlds/spaces\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds space\" }\n ,\"microsoft.mesh/worlds/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds spaces access policy\" }\n ,\"microsoft.mesh/worlds/templates\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds template\" }\n ,\"microsoft.mesh/worlds/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds templates access policy\" }\n ,\"microsoft.messagingcatalog/catalogs\": { \"SingularDisplayName\": \"Microsoft.MessagingCatalog catalog\" }\n ,\"microsoft.messagingconnectors/connectors\": { \"SingularDisplayName\": \"Microsoft.MessagingConnectors connector\" }\n ,\"microsoft.metaverse/metaverses\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverse\" }\n ,\"microsoft.metaverse/metaverses/events\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses event\" }\n ,\"microsoft.metaverse/metaverses/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses events access policy\" }\n ,\"microsoft.metaverse/metaverses/spaces\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses space\" }\n ,\"microsoft.metaverse/metaverses/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses spaces access policy\" }\n ,\"microsoft.metaverse/metaverses/templates\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses template\" }\n ,\"microsoft.metaverse/metaverses/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses templates access policy\" }\n ,\"microsoft.migrate/assessmentprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment project\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/clusters\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments cluster\" }\n ,\"microsoft.migrate/assessmentprojects/aksassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/assessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment\" }\n ,\"microsoft.migrate/assessmentprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/assessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments avs assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/avsassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business case\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/avssummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases avs summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedavsmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated avs machine\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated machine\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedsqlentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated sql entity\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated web app\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/iaassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases iaas summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/overviewsummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases overview summary\" }\n ,\"microsoft.migrate/assessmentprojects/businesscases/paassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases paas summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects group\" }\n ,\"microsoft.migrate/assessmentprojects/groups/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessments assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessments avs assessed machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql database\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql instance\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql machine\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/recommendedassessedentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments recommended assessed entity\" }\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessment\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments web app service plan\" }\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessment\" }\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/hypervcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects hypervcollector\" }\n ,\"microsoft.migrate/assessmentprojects/importcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects importcollector\" }\n ,\"microsoft.migrate/assessmentprojects/importsqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects import sql collector\" }\n ,\"microsoft.migrate/assessmentprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects machine\" }\n ,\"microsoft.migrate/assessmentprojects/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private endpoint connection\" }\n ,\"microsoft.migrate/assessmentprojects/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private link resource\" }\n ,\"microsoft.migrate/assessmentprojects/projectsummary\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects project summary\" }\n ,\"microsoft.migrate/assessmentprojects/servercollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects servercollector\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql database\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql instance\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql machine\" }\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/sqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sqlcollector\" }\n ,\"microsoft.migrate/assessmentprojects/vmwarecollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects vmwarecollector\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment option\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments assessed web app\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments summary\" }\n ,\"microsoft.migrate/assessmentprojects/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments web app service plan\" }\n ,\"microsoft.migrate/assessmentprojects/webappcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app collector\" }\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessment\" }\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessments summary\" }\n ,\"microsoft.migrate/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate project\" }\n ,\"microsoft.migrate/migrateprojects/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database instance\" }\n ,\"microsoft.migrate/migrateprojects/databases\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database\" }\n ,\"microsoft.migrate/migrateprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects machine\" }\n ,\"microsoft.migrate/migrateprojects/migrateevents\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects migrate event\" }\n ,\"microsoft.migrate/migrateprojects/solutions\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects solution\" }\n ,\"microsoft.migrate/modernizeprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize project\" }\n ,\"microsoft.migrate/modernizeprojects/deployedresources\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects deployed resource\" }\n ,\"microsoft.migrate/modernizeprojects/jobs\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects job\" }\n ,\"microsoft.migrate/modernizeprojects/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects jobs operation\" }\n ,\"microsoft.migrate/modernizeprojects/migrateagents\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agent\" }\n ,\"microsoft.migrate/modernizeprojects/migrateagents/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agents operation\" }\n ,\"microsoft.migrate/modernizeprojects/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects operation\" }\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployment\" }\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployments operation\" }\n ,\"microsoft.migrate/modernizeprojects/workloadinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instance\" }\n ,\"microsoft.migrate/modernizeprojects/workloadinstances/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instances operation\" }\n ,\"microsoft.migrate/movecollections\": { \"SingularDisplayName\": \"Microsoft.Migrate move collection\" }\n ,\"microsoft.migrate/movecollections/moveresources\": { \"SingularDisplayName\": \"Microsoft.Migrate move collections move resource\" }\n ,\"microsoft.migrate/projects\": { \"SingularDisplayName\": \"Migration project\" }\n ,\"microsoft.mission/approvals\": { \"SingularDisplayName\": \"Approval\" }\n ,\"microsoft.mission/catalogs\": { \"SingularDisplayName\": \"Catalog\" }\n ,\"microsoft.mission/communities\": { \"SingularDisplayName\": \"Community\" }\n ,\"microsoft.mission/communities/communityendpoints\": { \"SingularDisplayName\": \"Community endpoint\" }\n ,\"microsoft.mission/communities/transithubs\": { \"SingularDisplayName\": \"Transit hub\" }\n ,\"microsoft.mission/enclaveconnections\": { \"SingularDisplayName\": \"Enclave connection\" }\n ,\"microsoft.mission/externalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission external connection\" }\n ,\"microsoft.mission/internalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission internal connection\" }\n ,\"microsoft.mission/virtualenclaves\": { \"SingularDisplayName\": \"Enclave\" }\n ,\"microsoft.mission/virtualenclaves/enclaveendpoints\": { \"SingularDisplayName\": \"Enclave endpoint\" }\n ,\"microsoft.mission/virtualenclaves/endpoints\": { \"SingularDisplayName\": \"Endpoint\" }\n ,\"microsoft.mission/virtualenclaves/workloads\": { \"SingularDisplayName\": \"Workload\" }\n ,\"microsoft.mixedreality/objectanchorsaccounts\": { \"SingularDisplayName\": \"Object Anchors Account\" }\n ,\"microsoft.mixedreality/objectunderstandingaccounts\": { \"SingularDisplayName\": \"Object Understanding Account\" }\n ,\"microsoft.mixedreality/remoterenderingaccounts\": { \"SingularDisplayName\": \"Remote Rendering Account\" }\n ,\"microsoft.mixedreality/spatialanchorsaccounts\": { \"SingularDisplayName\": \"Spatial Anchors Account\" }\n ,\"microsoft.mixedreality/spatialmapsaccounts\": { \"SingularDisplayName\": \"Microsoft.MixedReality spatial maps account\" }\n ,\"microsoft.mobilenetwork/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork amf deployment\" }\n ,\"microsoft.mobilenetwork/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork cluster service\" }\n ,\"microsoft.mobilenetwork/mobilenetworks\": { \"SingularDisplayName\": \"Mobile Network\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/datanetworks\": { \"SingularDisplayName\": \"Data Network\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/services\": { \"SingularDisplayName\": \"Service\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/simpolicies\": { \"SingularDisplayName\": \"SIM Policy\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/sites\": { \"SingularDisplayName\": \"Mobile Network Site\" }\n ,\"microsoft.mobilenetwork/mobilenetworks/slices\": { \"SingularDisplayName\": \"Slice\" }\n ,\"microsoft.mobilenetwork/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nrf deployment\" }\n ,\"microsoft.mobilenetwork/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nssf deployment\" }\n ,\"microsoft.mobilenetwork/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork observability service\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes\": { \"SingularDisplayName\": \"Packet Core Control Plane\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes\": { \"SingularDisplayName\": \"Packet Core Data Plane\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes/attacheddatanetworks\": { \"SingularDisplayName\": \"Attached Data Network\" }\n ,\"microsoft.mobilenetwork/packetcorecontrolplaneversions\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork packet core control plane version\" }\n ,\"microsoft.mobilenetwork/radioaccessnetworks\": { \"SingularDisplayName\": \"Radio Access Network Insights\" }\n ,\"microsoft.mobilenetwork/sdmdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sdm deployment\" }\n ,\"microsoft.mobilenetwork/simgroups\": { \"SingularDisplayName\": \"SIM Group\" }\n ,\"microsoft.mobilenetwork/simgroups/sims\": { \"SingularDisplayName\": \"SIM\" }\n ,\"microsoft.mobilenetwork/sims\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sim\" }\n ,\"microsoft.mobilenetwork/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork smf deployment\" }\n ,\"microsoft.mobilenetwork/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork upf deployment\" }\n ,\"microsoft.mobilenetwork/virtualizedmmedeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork virtualized mme deployment\" }\n ,\"microsoft.mobilenetwork/vnfagentdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork vnf agent deployment\" }\n ,\"microsoft.mobilepacketcore/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore amf deployment\" }\n ,\"microsoft.mobilepacketcore/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore cluster service\" }\n ,\"microsoft.mobilepacketcore/networkfunctions\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore network function\" }\n ,\"microsoft.mobilepacketcore/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nrf deployment\" }\n ,\"microsoft.mobilepacketcore/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nssf deployment\" }\n ,\"microsoft.mobilepacketcore/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore observability service\" }\n ,\"microsoft.mobilepacketcore/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore smf deployment\" }\n ,\"microsoft.mobilepacketcore/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore upf deployment\" }\n ,\"microsoft.modsimworkbench/workbenches\": { \"SingularDisplayName\": \"Modeling and Simulation Workbench\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers\": { \"SingularDisplayName\": \"Chamber\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/connectors\": { \"SingularDisplayName\": \"Chamber Connector\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/filerequests\": { \"SingularDisplayName\": \"Chamber Data Pipeline File Request\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/files\": { \"SingularDisplayName\": \"Chamber Data Pipeline File\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/licenses\": { \"SingularDisplayName\": \"Chamber License\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/storages\": { \"SingularDisplayName\": \"Chamber Storage\" }\n ,\"microsoft.modsimworkbench/workbenches/chambers/workloads\": { \"SingularDisplayName\": \"Chamber VM\" }\n ,\"microsoft.modsimworkbench/workbenches/sharedstorages\": { \"SingularDisplayName\": \"Shared Storage\" }\n ,\"microsoft.monitor/accounts\": { \"SingularDisplayName\": \"Azure Monitor workspace\" }\n ,\"microsoft.monitor/investigations\": { \"SingularDisplayName\": \"Microsoft.Monitor investigation\" }\n ,\"microsoft.monitor/pipelinegroups\": { \"SingularDisplayName\": \"Azure Monitor pipeline\" }\n ,\"microsoft.mysqldiscovery/mysqlsites\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsite\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites agent\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites error summary\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/mysqlservers\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites my sqlserver\" }\n ,\"microsoft.mysqldiscovery/mysqlsites/summaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites summary\" }\n ,\"microsoft.netapp/netappaccounts\": { \"SingularDisplayName\": \"NetApp account\" }\n ,\"microsoft.netapp/netappaccounts/backuppolicies\": { \"SingularDisplayName\": \"Backup Policy\" }\n ,\"microsoft.netapp/netappaccounts/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools\": { \"SingularDisplayName\": \"Capacity pool\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes\": { \"SingularDisplayName\": \"Volume\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/volumequotarules\": { \"SingularDisplayName\": \"User and group quota\" }\n ,\"microsoft.netapp/netappaccounts/snapshotpolicies\": { \"SingularDisplayName\": \"Snapshot policy\" }\n ,\"microsoft.netapp/netappaccounts/volumegroups\": { \"SingularDisplayName\": \"VolumeGroup\" }\n ,\"microsoft.network/applicationgatewayavailablessloptions\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl option\" }\n ,\"microsoft.network/applicationgatewayavailablessloptions/predefinedpolicies\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl options predefined policy\" }\n ,\"microsoft.network/applicationgateways\": { \"SingularDisplayName\": \"Application gateway\" }\n ,\"microsoft.network/applicationgatewaywebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Application Gateway WAF policy\" }\n ,\"microsoft.network/applicationsecuritygroups\": { \"SingularDisplayName\": \"Application security group\" }\n ,\"microsoft.network/azurefirewalls\": { \"SingularDisplayName\": \"Firewall\" }\n ,\"microsoft.network/azurewebcategories\": { \"SingularDisplayName\": \"Microsoft.Network Azure web category\" }\n ,\"microsoft.network/bastionhosts\": { \"SingularDisplayName\": \"Bastion\" }\n ,\"microsoft.network/cloudserviceslots\": { \"SingularDisplayName\": \"Microsoft.Network cloud service slot\" }\n ,\"microsoft.network/connections\": { \"SingularDisplayName\": \"Connection\" }\n ,\"microsoft.network/customipprefixes\": { \"SingularDisplayName\": \"Custom IP Prefix\" }\n ,\"microsoft.network/ddoscustompolicies\": { \"SingularDisplayName\": \"Microsoft.Network DDoS custom policy\" }\n ,\"microsoft.network/ddosprotectionplans\": { \"SingularDisplayName\": \"DDoS protection plan\" }\n ,\"microsoft.network/dnsforwardingrulesets\": { \"SingularDisplayName\": \"DNS forwarding ruleset\" }\n ,\"microsoft.network/dnsresolverdomainlists\": { \"SingularDisplayName\": \"DNS Domain List\" }\n ,\"microsoft.network/dnsresolverpolicies\": { \"SingularDisplayName\": \"DNS Security Policy\" }\n ,\"microsoft.network/dnsresolvers\": { \"SingularDisplayName\": \"DNS private resolver\" }\n ,\"microsoft.network/dnszones\": { \"SingularDisplayName\": \"DNS zone\" }\n ,\"microsoft.network/dscpconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network DSCP configuration\" }\n ,\"microsoft.network/expressroutecircuits\": { \"SingularDisplayName\": \"ExpressRoute circuit\" }\n ,\"microsoft.network/expressroutecrossconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connection\" }\n ,\"microsoft.network/expressroutecrossconnections/peerings\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connections peering\" }\n ,\"microsoft.network/expressroutegateways\": { \"SingularDisplayName\": \"ExpressRoute Gateway\" }\n ,\"microsoft.network/expressroutegateways/expressrouteconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route gateways express route connection\" }\n ,\"microsoft.network/expressrouteports\": { \"SingularDisplayName\": \"ExpressRoute Direct\" }\n ,\"microsoft.network/expressrouteportslocations\": { \"SingularDisplayName\": \"Microsoft.Network express route ports location\" }\n ,\"microsoft.network/firewallpolicies\": { \"SingularDisplayName\": \"Firewall Policy\" }\n ,\"microsoft.network/frontdoors\": { \"SingularDisplayName\": \"Front Door and CDN profiles\" }\n ,\"microsoft.network/frontdoorwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Front Door WAF policy\" }\n ,\"microsoft.network/ipallocations\": { \"SingularDisplayName\": \"Microsoft.Network IP allocation\" }\n ,\"microsoft.network/ipgroups\": { \"SingularDisplayName\": \"IP Group\" }\n ,\"microsoft.network/loadbalancers\": { \"SingularDisplayName\": \"Load balancer\" }\n ,\"microsoft.network/localnetworkgateways\": { \"SingularDisplayName\": \"Local network gateway\" }\n ,\"microsoft.network/natgateways\": { \"SingularDisplayName\": \"NAT gateway\" }\n ,\"microsoft.network/networkexperimentprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profile\" }\n ,\"microsoft.network/networkexperimentprofiles/experiments\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profiles experiment\" }\n ,\"microsoft.network/networkinterfaces\": { \"SingularDisplayName\": \"Network interface\" }\n ,\"microsoft.network/networkmanagerconnections\": { \"SingularDisplayName\": \"Microsoft.Network network manager connection\" }\n ,\"microsoft.network/networkmanagers\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/connectivityconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/ipampools\": { \"SingularDisplayName\": \"IP address pool\" }\n ,\"microsoft.network/networkmanagers/networkgroups\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/routingconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/securityadminconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/securityuserconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\n ,\"microsoft.network/networkmanagers/verifierworkspaces\": { \"SingularDisplayName\": \"Verifier Workspace\" }\n ,\"microsoft.network/networkprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network profile\" }\n ,\"microsoft.network/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group\" }\n ,\"microsoft.network/networksecurityperimeters\": { \"SingularDisplayName\": \"Network Security Perimeter\" }\n ,\"microsoft.network/networksecurityperimeters/profiles\": { \"SingularDisplayName\": \"Network Security Perimeter Profile\" }\n ,\"microsoft.network/networkverifiers\": { \"SingularDisplayName\": \"Virtual Network Verifier\" }\n ,\"microsoft.network/networkvirtualappliances\": { \"SingularDisplayName\": \"Microsoft.Network network virtual appliance\" }\n ,\"microsoft.network/networkwatchers\": { \"SingularDisplayName\": \"Network Watcher\" }\n ,\"microsoft.network/networkwatchers/flowlogs\": { \"SingularDisplayName\": \"Flow log\" }\n ,\"microsoft.network/p2svpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Point to Site)\" }\n ,\"microsoft.network/privatednszones\": { \"SingularDisplayName\": \"Private DNS zone\" }\n ,\"microsoft.network/privatednszones/virtualnetworklinks\": { \"SingularDisplayName\": \"Virtual network link\" }\n ,\"microsoft.network/privateendpoints\": { \"SingularDisplayName\": \"Private endpoint\" }\n ,\"microsoft.network/privatelinkservices\": { \"SingularDisplayName\": \"Private link service\" }\n ,\"microsoft.network/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\n ,\"microsoft.network/publicipprefixes\": { \"SingularDisplayName\": \"Public IP Prefix\" }\n ,\"microsoft.network/routefilters\": { \"SingularDisplayName\": \"Route filter\" }\n ,\"microsoft.network/routetables\": { \"SingularDisplayName\": \"Route table\" }\n ,\"microsoft.network/securitypartnerproviders\": { \"SingularDisplayName\": \"Microsoft.Network security partner provider\" }\n ,\"microsoft.network/serviceendpointpolicies\": { \"SingularDisplayName\": \"Service endpoint policy\" }\n ,\"microsoft.network/trafficmanagergeographichierarchies\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager geographic hierarchy\" }\n ,\"microsoft.network/trafficmanagerprofiles\": { \"SingularDisplayName\": \"Traffic Manager profile\" }\n ,\"microsoft.network/trafficmanagerusermetricskeys\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager user metrics key\" }\n ,\"microsoft.network/virtualhubs\": { \"SingularDisplayName\": \"Microsoft.Network/virtualHub\" }\n ,\"microsoft.network/virtualnetworkgateways\": { \"SingularDisplayName\": \"Virtual network gateway\" }\n ,\"microsoft.network/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network\" }\n ,\"microsoft.network/virtualnetworktaps\": { \"SingularDisplayName\": \"Virtual network terminal access point\" }\n ,\"microsoft.network/virtualrouters\": { \"SingularDisplayName\": \"Microsoft.Network virtual router\" }\n ,\"microsoft.network/virtualrouters/peerings\": { \"SingularDisplayName\": \"Microsoft.Network virtual routers peering\" }\n ,\"microsoft.network/virtualwans\": { \"SingularDisplayName\": \"Virtual WAN\" }\n ,\"microsoft.network/vpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Site to Site)\" }\n ,\"microsoft.network/vpngateways/vpnconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connection\" }\n ,\"microsoft.network/vpngateways/vpnconnections/vpnlinkconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connections VPN link connection\" }\n ,\"microsoft.network/vpnserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network VPN server configuration\" }\n ,\"microsoft.network/vpnsites\": { \"SingularDisplayName\": \"Microsoft.Network VPN site\" }\n ,\"microsoft.network/vpnsites/vpnsitelinks\": { \"SingularDisplayName\": \"Microsoft.Network VPN sites VPN site link\" }\n ,\"microsoft.networkanalytics/dataconnectors\": { \"SingularDisplayName\": \"AIOps - Data Connector\" }\n ,\"microsoft.networkanalytics/datalakehouses\": { \"SingularDisplayName\": \"AIOps - Data LakeHouse\" }\n ,\"microsoft.networkanalytics/dataproducts\": { \"SingularDisplayName\": \"Azure Operator Insights ? Data Product\" }\n ,\"microsoft.networkanalytics/dataproducts/datatypes\": { \"SingularDisplayName\": \"Data Type\" }\n ,\"microsoft.networkanalytics/dataproductscatalogs\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics data products catalog\" }\n ,\"microsoft.networkanalytics/metricsingestionendpoints\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics metrics ingestion endpoint\" }\n ,\"microsoft.networkanalytics/networkanalyticsproducts\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics network analytics product\" }\n ,\"microsoft.networkcloud/baremetalmachines\": { \"SingularDisplayName\": \"Bare Metal Machine (Operator Nexus)\" }\n ,\"microsoft.networkcloud/cloudservicesnetworks\": { \"SingularDisplayName\": \"Cloud Services Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clustermanagers\": { \"SingularDisplayName\": \"Cluster Manager (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters\": { \"SingularDisplayName\": \"Cluster (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/baremetalmachinekeysets\": { \"SingularDisplayName\": \"Cluster Bare Metal Machine Key Set (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/bmckeysets\": { \"SingularDisplayName\": \"Cluster Baseboard Management Controller Key Set (Operator Nexus)\" }\n ,\"microsoft.networkcloud/clusters/metricsconfigurations\": { \"SingularDisplayName\": \"Cluster Metrics Configuration (Operator Nexus)\" }\n ,\"microsoft.networkcloud/edgeclustermachineskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster machine SKU\" }\n ,\"microsoft.networkcloud/edgeclusterruntimeversions\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster runtime version\" }\n ,\"microsoft.networkcloud/edgeclusters\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster\" }\n ,\"microsoft.networkcloud/edgeclusters/nodes\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge clusters node\" }\n ,\"microsoft.networkcloud/edgeclusterskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster SKU\" }\n ,\"microsoft.networkcloud/kubernetesclusters\": { \"SingularDisplayName\": \"Kubernetes Cluster (Operator Nexus)\" }\n ,\"microsoft.networkcloud/kubernetesclusters/agentpools\": { \"SingularDisplayName\": \"Agent Pool (Operator Nexus)\" }\n ,\"microsoft.networkcloud/kubernetesclusters/features\": { \"SingularDisplayName\": \"Kubernetes Cluster Feature (Operator Nexus)\" }\n ,\"microsoft.networkcloud/l2networks\": { \"SingularDisplayName\": \"Layer 2 Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/l3networks\": { \"SingularDisplayName\": \"Layer 3 Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/racks\": { \"SingularDisplayName\": \"Compute Rack (Operator Nexus)\" }\n ,\"microsoft.networkcloud/rackskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud rack SKU\" }\n ,\"microsoft.networkcloud/registrationhubs\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hub\" }\n ,\"microsoft.networkcloud/registrationhubs/images\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs image\" }\n ,\"microsoft.networkcloud/registrationhubs/machines\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs machine\" }\n ,\"microsoft.networkcloud/storageappliances\": { \"SingularDisplayName\": \"Storage Appliance (Operator Nexus)\" }\n ,\"microsoft.networkcloud/trunkednetworks\": { \"SingularDisplayName\": \"Trunked Network (Operator Nexus)\" }\n ,\"microsoft.networkcloud/virtualmachines\": { \"SingularDisplayName\": \"Virtual Machine (Operator Nexus)\" }\n ,\"microsoft.networkcloud/virtualmachines/consoles\": { \"SingularDisplayName\": \"Virtual Machine Console (Operator Nexus)\" }\n ,\"microsoft.networkcloud/volumes\": { \"SingularDisplayName\": \"Volume (Operator Nexus)\" }\n ,\"microsoft.networkfunction/azuretrafficcollectors\": { \"SingularDisplayName\": \"ExpressRoute traffic collector\" }\n ,\"microsoft.networkfunction/meshvpns\": { \"SingularDisplayName\": \"Mesh VPN\" }\n ,\"microsoft.nexusidentity/identitycontrollers\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity controller\" }\n ,\"microsoft.nexusidentity/identitysets\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity set\" }\n ,\"microsoft.notebooks/notebookproxies\": { \"SingularDisplayName\": \"Microsoft.Notebooks notebook proxy\" }\n ,\"microsoft.notificationhubs/namespaces\": { \"SingularDisplayName\": \"Notification Hub Namespace\" }\n ,\"microsoft.notificationhubs/namespaces/notificationhubs\": { \"SingularDisplayName\": \"Notification Hub\" }\n ,\"microsoft.objectstore/osnamespaces\": { \"SingularDisplayName\": \"Microsoft.ObjectStore os namespace\" }\n })[tolower(id)]\n}\n", - "$fxv#3": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_4(id: string) {\n dynamic({\n \"microsoft.offazure/hypervsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv site\" }\n ,\"microsoft.offazure/hypervsites/clusters\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites cluster\" }\n ,\"microsoft.offazure/hypervsites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites host\" }\n ,\"microsoft.offazure/hypervsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites job\" }\n ,\"microsoft.offazure/hypervsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machine\" }\n ,\"microsoft.offazure/hypervsites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machines software inventory\" }\n ,\"microsoft.offazure/hypervsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites operations statu\" }\n ,\"microsoft.offazure/hypervsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites run as account\" }\n ,\"microsoft.offazure/importsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure import site\" }\n ,\"microsoft.offazure/importsites/deletejobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites delete job\" }\n ,\"microsoft.offazure/importsites/exportjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites export job\" }\n ,\"microsoft.offazure/importsites/importjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites import job\" }\n ,\"microsoft.offazure/importsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites job\" }\n ,\"microsoft.offazure/importsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites machine\" }\n ,\"microsoft.offazure/mastersites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master site\" }\n ,\"microsoft.offazure/mastersites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites operations statu\" }\n ,\"microsoft.offazure/mastersites/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private endpoint connection\" }\n ,\"microsoft.offazure/mastersites/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private link resource\" }\n ,\"microsoft.offazure/mastersites/sqlsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql site\" }\n ,\"microsoft.offazure/mastersites/sqlsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites discovery site data source\" }\n ,\"microsoft.offazure/mastersites/sqlsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites job\" }\n ,\"microsoft.offazure/mastersites/sqlsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites operations statu\" }\n ,\"microsoft.offazure/mastersites/sqlsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites run as account\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqlavailabilitygroups\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql availability group\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqldatabases\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql database\" }\n ,\"microsoft.offazure/mastersites/sqlsites/sqlservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql server\" }\n ,\"microsoft.offazure/mastersites/webappsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app site\" }\n ,\"microsoft.offazure/mastersites/webappsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites discovery site data source\" }\n ,\"microsoft.offazure/mastersites/webappsites/extendedmachines\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites extended machine\" }\n ,\"microsoft.offazure/mastersites/webappsites/iiswebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web application\" }\n ,\"microsoft.offazure/mastersites/webappsites/iiswebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web server\" }\n ,\"microsoft.offazure/mastersites/webappsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites runasaccount\" }\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web application\" }\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web server\" }\n ,\"microsoft.offazure/serversites\": { \"SingularDisplayName\": \"Microsoft.OffAzure server site\" }\n ,\"microsoft.offazure/serversites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites job\" }\n ,\"microsoft.offazure/serversites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machine\" }\n ,\"microsoft.offazure/serversites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machines software inventory\" }\n ,\"microsoft.offazure/serversites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites operations statu\" }\n ,\"microsoft.offazure/serversites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites run as account\" }\n ,\"microsoft.offazure/vmwaresites\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware site\" }\n ,\"microsoft.offazure/vmwaresites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites host\" }\n ,\"microsoft.offazure/vmwaresites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites job\" }\n ,\"microsoft.offazure/vmwaresites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machine\" }\n ,\"microsoft.offazure/vmwaresites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machines software inventory\" }\n ,\"microsoft.offazure/vmwaresites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites operations statu\" }\n ,\"microsoft.offazure/vmwaresites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites run as account\" }\n ,\"microsoft.offazure/vmwaresites/vcenters\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites vcenter\" }\n ,\"microsoft.offazurespringboot/springbootsites\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsite\" }\n ,\"microsoft.offazurespringboot/springbootsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites error summary\" }\n ,\"microsoft.offazurespringboot/springbootsites/springbootapps\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootapp\" }\n ,\"microsoft.offazurespringboot/springbootsites/springbootservers\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootserver\" }\n ,\"microsoft.offazurespringboot/springbootsites/summaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites summary\" }\n ,\"microsoft.onlineexperimentation/workspaces\": { \"SingularDisplayName\": \"Online Experimentation Workspace\" }\n ,\"microsoft.openenergyplatform/energyservices\": { \"SingularDisplayName\": \"Azure Data Manager for Energy\" }\n ,\"microsoft.openlogisticsplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspace\" }\n ,\"microsoft.openlogisticsplatform/workspaces/applicationregistrations\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application registration\" }\n ,\"microsoft.openlogisticsplatform/workspaces/applications\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application\" }\n ,\"microsoft.openlogisticsplatform/workspaces/eventgridfilters\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces event grid filter\" }\n ,\"microsoft.openlogisticsplatform/workspaces/shares\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share\" }\n ,\"microsoft.openlogisticsplatform/workspaces/sharesubscriptions\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share subscription\" }\n ,\"microsoft.operationalinsights/clusters\": { \"SingularDisplayName\": \"Log Analytics dedicated cluster\" }\n ,\"microsoft.operationalinsights/querypacks\": { \"SingularDisplayName\": \"Log Analytics query pack\" }\n ,\"microsoft.operationalinsights/workspaces\": { \"SingularDisplayName\": \"Log Analytics workspace\" }\n ,\"microsoft.operationsmanagement/managementassociations\": { \"SingularDisplayName\": \"Microsoft.OperationsManagement management association\" }\n ,\"microsoft.operationsmanagement/solutions\": { \"SingularDisplayName\": \"Solution\" }\n ,\"microsoft.operatorvoicemail/operatorvoicemailinstances\": { \"SingularDisplayName\": \"Microsoft.OperatorVoicemail operator voicemail instance\" }\n ,\"microsoft.oraclediscovery/oraclesites\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle site\" }\n ,\"microsoft.oraclediscovery/oraclesites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites error summary\" }\n ,\"microsoft.oraclediscovery/oraclesites/oracledatabases\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle database\" }\n ,\"microsoft.oraclediscovery/oraclesites/oracleservers\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle server\" }\n ,\"microsoft.oraclediscovery/oraclesites/summaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites summary\" }\n ,\"microsoft.orbital/cloudaccessrouters\": { \"SingularDisplayName\": \"Cloud Access Router\" }\n ,\"microsoft.orbital/contactprofiles\": { \"SingularDisplayName\": \"Contact Profile\" }\n ,\"microsoft.orbital/edgesites\": { \"SingularDisplayName\": \"Edge Site\" }\n ,\"microsoft.orbital/geocatalogs\": { \"SingularDisplayName\": \"GeoCatalog\" }\n ,\"microsoft.orbital/globalcommunicationssites\": { \"SingularDisplayName\": \"Microsoft.Orbital global communications site\" }\n ,\"microsoft.orbital/groundstations\": { \"SingularDisplayName\": \"Ground Station\" }\n ,\"microsoft.orbital/l2connections\": { \"SingularDisplayName\": \"L2 Connection\" }\n ,\"microsoft.orbital/sdwancontrollers\": { \"SingularDisplayName\": \"SDWAN Controller\" }\n ,\"microsoft.orbital/spacecrafts\": { \"SingularDisplayName\": \"Spacecraft\" }\n ,\"microsoft.orbital/spacecrafts/contacts\": { \"SingularDisplayName\": \"Contact\" }\n ,\"microsoft.orbital/terminals\": { \"SingularDisplayName\": \"Cloud Access Terminal\" }\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrence\" }\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences/operationresult\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrences operation result\" }\n ,\"microsoft.peering/peerasns\": { \"SingularDisplayName\": \"Microsoft.Peering peer asn\" }\n ,\"microsoft.peering/peerings\": { \"SingularDisplayName\": \"Peering\" }\n ,\"microsoft.peering/peerings/registeredasns\": { \"SingularDisplayName\": \"Registered ASN\" }\n ,\"microsoft.peering/peerings/registeredprefixes\": { \"SingularDisplayName\": \"Registered prefix\" }\n ,\"microsoft.peering/peeringservices\": { \"SingularDisplayName\": \"Peering Service\" }\n ,\"microsoft.peering/peeringservices/prefixes\": { \"SingularDisplayName\": \"Peering Service Prefix\" }\n ,\"microsoft.pki/pkis\": { \"SingularDisplayName\": \"Microsoft.Pki PKI\" }\n ,\"microsoft.pki/pkis/certificateauthorities\": { \"SingularDisplayName\": \"Microsoft.Pki pkis certificate authority\" }\n ,\"microsoft.pki/pkis/enrollmentpolicies\": { \"SingularDisplayName\": \"Microsoft.Pki pkis enrollment policy\" }\n ,\"microsoft.policyinsights/attestations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights attestation\" }\n ,\"microsoft.policyinsights/policymetadata\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights policy metadata\" }\n ,\"microsoft.policyinsights/remediations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights remediation\" }\n ,\"microsoft.portal/consoles\": { \"SingularDisplayName\": \"Microsoft.Portal console\" }\n ,\"microsoft.portal/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\n ,\"microsoft.portal/tenantconfigurations\": { \"SingularDisplayName\": \"Microsoft.Portal tenant configuration\" }\n ,\"microsoft.portal/usersettings\": { \"SingularDisplayName\": \"Microsoft.Portal user setting\" }\n ,\"microsoft.portal/virtual-privatedashboards\": { \"SingularDisplayName\": \"Private dashboard\" }\n ,\"microsoft.portalservices/copilotsettings\": { \"SingularDisplayName\": \"Microsoft.PortalServices copilot setting\" }\n ,\"microsoft.portalservices/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\n ,\"microsoft.portalservices/extensions\": { \"SingularDisplayName\": \"Portal Extension\" }\n ,\"microsoft.portalservices/extensions/deployments\": { \"SingularDisplayName\": \"Extension Deployment\" }\n ,\"microsoft.portalservices/extensions/slots\": { \"SingularDisplayName\": \"Extension Slot\" }\n ,\"microsoft.portalservices/extensions/versions\": { \"SingularDisplayName\": \"Extension Version\" }\n ,\"microsoft.portalservices/settings\": { \"SingularDisplayName\": \"Microsoft.PortalServices setting\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private endpoint connection\" }\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private link resource\" }\n ,\"microsoft.powerbi/workspacecollections\": { \"SingularDisplayName\": \"Microsoft.PowerBI workspace collection\" }\n ,\"microsoft.powerbidedicated/autoscalevcores\": { \"SingularDisplayName\": \"Microsoft.PowerBIDedicated auto scale vcore\" }\n ,\"microsoft.powerbidedicated/capacities\": { \"SingularDisplayName\": \"Power BI Embedded\" }\n ,\"microsoft.powerplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.PowerPlatform account\" }\n ,\"microsoft.premonition/libraries\": { \"SingularDisplayName\": \"Microsoft.Premonition library\" }\n ,\"microsoft.premonition/libraries/analyses\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries analyse\" }\n ,\"microsoft.premonition/libraries/samples\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries sample\" }\n ,\"microsoft.professionalservice/resources\": { \"SingularDisplayName\": \"Professional Service\" }\n ,\"microsoft.programmableconnectivity/gateways\": { \"SingularDisplayName\": \"APC Gateway\" }\n ,\"microsoft.programmableconnectivity/operatorapiconnections\": { \"SingularDisplayName\": \"APC Operator API Connection\" }\n ,\"microsoft.programmableconnectivity/operatorapiplans\": { \"SingularDisplayName\": \"APC Operator API Plan\" }\n ,\"microsoft.proposal/proposals\": { \"SingularDisplayName\": \"Microsoft.Proposal proposal\" }\n ,\"microsoft.providerhub/providerregistrations\": { \"SingularDisplayName\": \"Resource Provider as a Service\" }\n ,\"microsoft.providerhub/providerregistrations/customrollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.providerhub/providerregistrations/defaultrollouts\": { \"SingularDisplayName\": \"Rollout\" }\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\n ,\"microsoft.providerhubdevtest/regionalstresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest regional stresstest\" }\n ,\"microsoft.providerhubdevtest/stresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest stresstest\" }\n ,\"microsoft.purview/accounts\": { \"SingularDisplayName\": \"Microsoft Purview account\" }\n ,\"microsoft.quantum/provideraccounts\": { \"SingularDisplayName\": \"Microsoft.Quantum provider account\" }\n ,\"microsoft.quantum/workspaces\": { \"SingularDisplayName\": \"Quantum Workspace\" }\n ,\"microsoft.quota/groupquotas\": { \"SingularDisplayName\": \"Microsoft.Quota group quota\" }\n ,\"microsoft.quota/groupquotas/groupquotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas group quota request\" }\n ,\"microsoft.quota/groupquotas/quotaallocationrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation request\" }\n ,\"microsoft.quota/groupquotas/quotaallocations\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation\" }\n ,\"microsoft.quota/groupquotas/subscriptionrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription request\" }\n ,\"microsoft.quota/groupquotas/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription\" }\n ,\"microsoft.quota/quotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota quota request\" }\n ,\"microsoft.quota/quotas\": { \"SingularDisplayName\": \"Microsoft.Quota quota\" }\n ,\"microsoft.quota/usages\": { \"SingularDisplayName\": \"Microsoft.Quota usage\" }\n ,\"microsoft.recommendationsservice/accounts\": { \"SingularDisplayName\": \"Intelligent Recommendations Account\" }\n ,\"microsoft.recommendationsservice/accounts/modeling\": { \"SingularDisplayName\": \"Modeling\" }\n ,\"microsoft.recommendationsservice/accounts/serviceendpoints\": { \"SingularDisplayName\": \"Service Endpoint\" }\n ,\"microsoft.recoveryservices/replicationeligibilityresults\": { \"SingularDisplayName\": \"Microsoft.RecoveryServices replication eligibility result\" }\n ,\"microsoft.recoveryservices/vaults\": { \"SingularDisplayName\": \"Recovery Services vault\" }\n ,\"microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems\": { \"SingularDisplayName\": \"Backup Item\" }\n ,\"microsoft.recoveryservicesbvtd/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD\" }\n ,\"microsoft.recoveryservicesbvtd2/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD2\" }\n ,\"microsoft.recoveryservicesintd/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD\" }\n ,\"microsoft.recoveryservicesintd2/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD2\" }\n ,\"microsoft.redhatopenshift/openshiftclusters\": { \"SingularDisplayName\": \"Azure Red Hat OpenShift cluster\" }\n ,\"microsoft.relationships/dependencyof\": { \"SingularDisplayName\": \"Dependency Relationship\" }\n ,\"microsoft.relationships/servicegroupmember\": { \"SingularDisplayName\": \"Service group member relationship\" }\n ,\"microsoft.relationships/servicegrouprelationships\": { \"SingularDisplayName\": \"Connected Resource\" }\n ,\"microsoft.relay/namespaces\": { \"SingularDisplayName\": \"Relay\" }\n ,\"microsoft.relay/namespaces/hybridconnections\": { \"SingularDisplayName\": \"Hybrid connection\" }\n ,\"microsoft.relay/namespaces/wcfrelays\": { \"SingularDisplayName\": \"WCF relay\" }\n ,\"microsoft.resilience/resiliencestates\": { \"SingularDisplayName\": \"Microsoft.Resilience resilience state\" }\n ,\"microsoft.resourceconnector/appliances\": { \"SingularDisplayName\": \"Resource bridge\" }\n ,\"microsoft.resourcegraph/queries\": { \"SingularDisplayName\": \"Resource Graph query\" }\n ,\"microsoft.resourcehealth/availabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth availability statuse\" }\n ,\"microsoft.resourcehealth/childavailabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth child availability statuse\" }\n ,\"microsoft.resourcehealth/emergingissues\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth emerging issue\" }\n ,\"microsoft.resourcehealth/events\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth event\" }\n ,\"microsoft.resourcehealth/events/impactedresources\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth events impacted resource\" }\n ,\"microsoft.resourcehealth/metadata\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth metadata\" }\n ,\"microsoft.resources/builtintemplatespecs\": { \"SingularDisplayName\": \"Built-in template spec\" }\n ,\"microsoft.resources/changes\": { \"SingularDisplayName\": \"Microsoft.Resources change\" }\n ,\"microsoft.resources/databoundaries\": { \"SingularDisplayName\": \"Microsoft.Resources data boundary\" }\n ,\"microsoft.resources/deletedresources\": { \"SingularDisplayName\": \"Recycle Bin\" }\n ,\"microsoft.resources/deployments\": { \"SingularDisplayName\": \"Microsoft.Resources deployment\" }\n ,\"microsoft.resources/deployments/operations\": { \"SingularDisplayName\": \"Microsoft.Resources deployments operation\" }\n ,\"microsoft.resources/deploymentscripts\": { \"SingularDisplayName\": \"Deployment Script\" }\n ,\"microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\n ,\"microsoft.resources/mobobrokers\": { \"SingularDisplayName\": \"Microsoft.Resources mobo broker\" }\n ,\"microsoft.resources/resourcechange\": { \"SingularDisplayName\": \"Change Analysis\" }\n ,\"microsoft.resources/resourcechanges\": { \"SingularDisplayName\": \"Resource change\" }\n ,\"microsoft.resources/resourcegraphvisualizer\": { \"SingularDisplayName\": \"Resource Graph Visualizer\" }\n ,\"microsoft.resources/resourcegroups\": { \"SingularDisplayName\": \"Microsoft.Resources resource group\" }\n ,\"microsoft.resources/resources\": { \"SingularDisplayName\": \"Resource\" }\n ,\"microsoft.resources/snapshots\": { \"SingularDisplayName\": \"Microsoft.Resources snapshot\" }\n ,\"microsoft.resources/subscriptions\": { \"SingularDisplayName\": \"Subscription\" }\n ,\"microsoft.resources/subscriptions/resourcegroups\": { \"SingularDisplayName\": \"Resource group\" }\n ,\"microsoft.resources/tags\": { \"SingularDisplayName\": \"Microsoft.Resources tag\" }\n ,\"microsoft.resources/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\n ,\"microsoft.resources/virtualsubscriptionsforresourcepicker\": { \"SingularDisplayName\": \"Subscription\" }\n ,\"microsoft.saas/applications\": { \"SingularDisplayName\": \"Software as a Service (classic)\" }\n ,\"microsoft.saas/resources\": { \"SingularDisplayName\": \"SaaS\" }\n ,\"microsoft.saas/saasresources\": { \"SingularDisplayName\": \"SaaS (classic)\" }\n ,\"microsoft.saashub/cloudservices\": { \"SingularDisplayName\": \"Microsoft.SaaSHub cloud service\" }\n ,\"microsoft.saashub/cloudservices/hidden\": { \"SingularDisplayName\": \"Microsoft SaaS\" }\n ,\"microsoft.saashub/saasresources\": { \"SingularDisplayName\": \"Microsoft.SaaSHub saas resource\" }\n ,\"microsoft.salescopilot/conversationintelligencerecordingaccounts\": { \"SingularDisplayName\": \"Microsoft.SalesCopilot conversation intelligence recording account\" }\n ,\"microsoft.scheduler/jobcollections\": { \"SingularDisplayName\": \"Scheduler job collection\" }\n ,\"microsoft.scheduler/jobcollections/jobs\": { \"SingularDisplayName\": \"Scheduler job\" }\n ,\"microsoft.scom/managedinstances\": { \"SingularDisplayName\": \"SCOM managed instance\" }\n ,\"microsoft.scvmm/availabilitysets\": { \"SingularDisplayName\": \"Microsoft.ScVmm availability set\" }\n ,\"microsoft.scvmm/clouds\": { \"SingularDisplayName\": \"Microsoft.ScVmm cloud\" }\n ,\"microsoft.scvmm/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instance\" }\n ,\"microsoft.scvmm/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances guest agent\" }\n ,\"microsoft.scvmm/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances hybrid identity metadata\" }\n ,\"microsoft.scvmm/virtualmachines\": { \"SingularDisplayName\": \"SCVMM virtual machine - Azure Arc\" }\n ,\"microsoft.scvmm/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine template\" }\n ,\"microsoft.scvmm/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual network\" }\n ,\"microsoft.scvmm/vmmservers\": { \"SingularDisplayName\": \"SCVMM management server\" }\n ,\"microsoft.search/searchservices\": { \"SingularDisplayName\": \"Search service\" }\n ,\"microsoft.secretmanagementsampleprovider/forecasts\": { \"SingularDisplayName\": \"Microsoft.SecretManagementSampleProvider forecast\" }\n ,\"microsoft.secretsynccontroller/azurekeyvaultsecretproviderclasses\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController Azure key vault secret provider class\" }\n ,\"microsoft.secretsynccontroller/secretsyncs\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController secret sync\" }\n ,\"microsoft.security/adaptivenetworkhardenings\": { \"SingularDisplayName\": \"Microsoft.Security adaptive network hardening\" }\n ,\"microsoft.security/advancedthreatprotectionsettings\": { \"SingularDisplayName\": \"Microsoft.Security advanced threat protection setting\" }\n ,\"microsoft.security/alertssuppressionrules\": { \"SingularDisplayName\": \"Microsoft.Security alerts suppression rule\" }\n ,\"microsoft.security/apicollections\": { \"SingularDisplayName\": \"Microsoft.Security API collection\" }\n ,\"microsoft.security/applications\": { \"SingularDisplayName\": \"Microsoft.Security application\" }\n ,\"microsoft.security/assessmentmetadata\": { \"SingularDisplayName\": \"Microsoft.Security assessment metadata\" }\n ,\"microsoft.security/assessments\": { \"SingularDisplayName\": \"Microsoft.Security assessment\" }\n ,\"microsoft.security/assessments/governanceassignments\": { \"SingularDisplayName\": \"Microsoft.Security assessments governance assignment\" }\n ,\"microsoft.security/assessments/subassessments\": { \"SingularDisplayName\": \"Microsoft.Security assessments sub assessment\" }\n ,\"microsoft.security/assignments\": { \"SingularDisplayName\": \"Microsoft.Security assignment\" }\n ,\"microsoft.security/automations\": { \"SingularDisplayName\": \"Microsoft.Security automation\" }\n ,\"microsoft.security/autoprovisioningsettings\": { \"SingularDisplayName\": \"Microsoft.Security auto provisioning setting\" }\n ,\"microsoft.security/complianceresults\": { \"SingularDisplayName\": \"Microsoft.Security compliance result\" }\n ,\"microsoft.security/compliances\": { \"SingularDisplayName\": \"Microsoft.Security compliance\" }\n ,\"microsoft.security/connectors\": { \"SingularDisplayName\": \"Microsoft.Security connector\" }\n ,\"microsoft.security/customassessmentautomations\": { \"SingularDisplayName\": \"Microsoft.Security custom assessment automation\" }\n ,\"microsoft.security/defenderforstoragesettings\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage setting\" }\n ,\"microsoft.security/defenderforstoragesettings/malwarescans\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage settings malware scan\" }\n ,\"microsoft.security/devicesecuritygroups\": { \"SingularDisplayName\": \"Microsoft.Security device security group\" }\n ,\"microsoft.security/governancerules\": { \"SingularDisplayName\": \"Microsoft.Security governance rule\" }\n ,\"microsoft.security/governancerules/operationresults\": { \"SingularDisplayName\": \"Microsoft.Security governance rules operation result\" }\n ,\"microsoft.security/healthreports\": { \"SingularDisplayName\": \"Microsoft.Security health report\" }\n ,\"microsoft.security/informationprotectionpolicies\": { \"SingularDisplayName\": \"Microsoft.Security information protection policy\" }\n ,\"microsoft.security/iotsecuritysolutions\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solution\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics model\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated alert\" }\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated recommendation\" }\n ,\"microsoft.security/iotsecuritysolutions/iotalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert\" }\n ,\"microsoft.security/iotsecuritysolutions/iotalerttypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert type\" }\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation\" }\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendationtypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation type\" }\n ,\"microsoft.security/locations/alerts\": { \"SingularDisplayName\": \"Security Alert\" }\n ,\"microsoft.security/mdeonboardings\": { \"SingularDisplayName\": \"Microsoft.Security mde onboarding\" }\n ,\"microsoft.security/pricings\": { \"SingularDisplayName\": \"Defender for Cloud\" }\n ,\"microsoft.security/pricings/securityoperators\": { \"SingularDisplayName\": \"Microsoft.Security pricings security operator\" }\n ,\"microsoft.security/regulatorycompliancestandards\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standard\" }\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance control\" }\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols/regulatorycomplianceassessments\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance controls regulatory compliance assessment\" }\n ,\"microsoft.security/securescores\": { \"SingularDisplayName\": \"Microsoft.Security secure score\" }\n ,\"microsoft.security/securityconnectors\": { \"SingularDisplayName\": \"Microsoft.Security security connector\" }\n ,\"microsoft.security/securityconnectors/devops\": { \"SingularDisplayName\": \"Microsoft.Security security connectors devop\" }\n ,\"microsoft.security/securitycontacts\": { \"SingularDisplayName\": \"Microsoft.Security security contact\" }\n ,\"microsoft.security/sensitivitysettings\": { \"SingularDisplayName\": \"Microsoft.Security sensitivity setting\" }\n ,\"microsoft.security/servervulnerabilityassessments\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessment\" }\n ,\"microsoft.security/servervulnerabilityassessmentssettings\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessments setting\" }\n ,\"microsoft.security/settings\": { \"SingularDisplayName\": \"Microsoft.Security setting\" }\n ,\"microsoft.security/standards\": { \"SingularDisplayName\": \"Microsoft.Security standard\" }\n ,\"microsoft.security/workspacesettings\": { \"SingularDisplayName\": \"Microsoft.Security workspace setting\" }\n ,\"microsoft.securitycopilot/capacities\": { \"SingularDisplayName\": \"Microsoft Security compute capacity\" }\n ,\"microsoft.securitydetonation/chambers\": { \"SingularDisplayName\": \"Security Detonation Chamber\" }\n ,\"microsoft.securityinsightsarg/sentinel\": { \"SingularDisplayName\": \"Microsoft Sentinel\" }\n ,\"microsoft.sentinelplatformservices/sentinelplatformservices\": { \"SingularDisplayName\": \"Microsoft.SentinelPlatformServices sentinel platform service\" }\n ,\"microsoft.serialconsole/consoleservices\": { \"SingularDisplayName\": \"Microsoft.SerialConsole console service\" }\n ,\"microsoft.serialconsole/serialports\": { \"SingularDisplayName\": \"Microsoft.SerialConsole serial port\" }\n ,\"microsoft.servicebus/namespaces\": { \"SingularDisplayName\": \"Service Bus namespace\" }\n ,\"microsoft.servicebus/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Service Bus Geo-DR Alias\" }\n ,\"microsoft.servicebus/namespaces/queues\": { \"SingularDisplayName\": \"Service Bus queue\" }\n ,\"microsoft.servicebus/namespaces/topics\": { \"SingularDisplayName\": \"Service Bus topic\" }\n ,\"microsoft.servicebus/namespaces/topics/subscriptions\": { \"SingularDisplayName\": \"Service Bus Subscription\" }\n ,\"microsoft.servicefabric/clusters\": { \"SingularDisplayName\": \"Service Fabric cluster\" }\n ,\"microsoft.servicefabric/managedclusters\": { \"SingularDisplayName\": \"Service Fabric managed cluster\" }\n ,\"microsoft.servicefabricmesh/applications\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh application\" }\n ,\"microsoft.servicefabricmesh/applications/services\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications service\" }\n ,\"microsoft.servicefabricmesh/applications/services/replicas\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications services replica\" }\n ,\"microsoft.servicefabricmesh/gateways\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh gateway\" }\n ,\"microsoft.servicefabricmesh/networks\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh network\" }\n ,\"microsoft.servicefabricmesh/secrets\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secret\" }\n ,\"microsoft.servicefabricmesh/secrets/values\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secrets value\" }\n ,\"microsoft.servicefabricmesh/volumes\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh volume\" }\n ,\"microsoft.servicelinker/dryruns\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker dryrun\" }\n ,\"microsoft.servicelinker/linkers\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker linker\" }\n ,\"microsoft.servicenetworking/trafficcontrollers\": { \"SingularDisplayName\": \"Application Gateway for Containers\" }\n ,\"microsoft.serviceshub/connectors\": { \"SingularDisplayName\": \"Services Hub Connector\" }\n ,\"microsoft.signalrservice/signalr\": { \"SingularDisplayName\": \"SignalR\" }\n ,\"microsoft.signalrservice/signalr/replicas\": { \"SingularDisplayName\": \"SignalR Replica\" }\n ,\"microsoft.signalrservice/webpubsub\": { \"SingularDisplayName\": \"Web PubSub Service\" }\n ,\"microsoft.signalrservice/webpubsub/replicas\": { \"SingularDisplayName\": \"Web PubSub Service Replica\" }\n ,\"microsoft.skytap/billingnodes\": { \"SingularDisplayName\": \"Microsoft.Skytap billing node\" }\n ,\"microsoft.skytap/interfaces\": { \"SingularDisplayName\": \"Microsoft.Skytap interface\" }\n ,\"microsoft.skytap/nodes\": { \"SingularDisplayName\": \"Microsoft.Skytap node\" }\n ,\"microsoft.softwareplan/hybridusebenefits\": { \"SingularDisplayName\": \"Microsoft.SoftwarePlan hybrid use benefit\" }\n ,\"microsoft.solutions/applicationdefinitions\": { \"SingularDisplayName\": \"Service catalog managed application definition\" }\n ,\"microsoft.solutions/applications\": { \"SingularDisplayName\": \"Managed application\" }\n ,\"microsoft.solutions/jitrequests\": { \"SingularDisplayName\": \"Microsoft.Solutions JIT request\" }\n ,\"microsoft.sovereign/landingzoneaccounts\": { \"SingularDisplayName\": \"Landing zone account\" }\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\n ,\"microsoft.sovereign/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\n ,\"microsoft.sovereign/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\n ,\"microsoft.sovereign/transparencylogs\": { \"SingularDisplayName\": \"Transparency log\" }\n ,\"microsoft.sql/azuresql\": { \"SingularDisplayName\": \"Azure SQL resource\" }\n ,\"microsoft.sql/instancepools\": { \"SingularDisplayName\": \"Instance pool\" }\n ,\"microsoft.sql/managedinstances\": { \"SingularDisplayName\": \"SQL managed instance\" }\n ,\"microsoft.sql/managedinstances/databases\": { \"SingularDisplayName\": \"Managed database\" }\n ,\"microsoft.sql/servers\": { \"SingularDisplayName\": \"SQL server\" }\n ,\"microsoft.sql/servers/databases\": { \"SingularDisplayName\": \"SQL database\" }\n ,\"microsoft.sql/servers/elasticpools\": { \"SingularDisplayName\": \"SQL elastic pool\" }\n ,\"microsoft.sql/servers/jobagents\": { \"SingularDisplayName\": \"Elastic Job agent\" }\n ,\"microsoft.sql/virtualclusters\": { \"SingularDisplayName\": \"Virtual cluster\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine group\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups/availabilitygrouplisteners\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine groups availability group listener\" }\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachines\": { \"SingularDisplayName\": \"SQL virtual machine\" }\n ,\"microsoft.standbypool/standbycontainergrouppools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pool\" }\n ,\"microsoft.standbypool/standbycontainergrouppools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pools runtime view\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pool\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools runtime view\" }\n ,\"microsoft.standbypool/standbyvirtualmachinepools/standbyvirtualmachines\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools standby virtual machine\" }\n ,\"microsoft.storage/storageaccounts\": { \"SingularDisplayName\": \"Storage account\" }\n ,\"microsoft.storageactions/storagetasks\": { \"SingularDisplayName\": \"Storage task - Azure Storage Actions\" }\n ,\"microsoft.storagecache/amlfilesystems\": { \"SingularDisplayName\": \"Azure Managed Lustre\" }\n ,\"microsoft.storagecache/caches\": { \"SingularDisplayName\": \"HPC cache\" }\n ,\"microsoft.storagediscovery/storagediscoveryworkspaces\": { \"SingularDisplayName\": \"Storage Discovery workspace\" }\n ,\"microsoft.storagehub/all\": { \"SingularDisplayName\": \"All resources\" }\n ,\"microsoft.storagehub/policycomplianceresources\": { \"SingularDisplayName\": \"Policy compliance\" }\n ,\"microsoft.storageinsights/storagecollectionrules\": { \"SingularDisplayName\": \"Microsoft.StorageInsights storage collection rule\" }\n ,\"microsoft.storagemover/storagemovers\": { \"SingularDisplayName\": \"Storage mover\" }\n ,\"microsoft.storagepool/diskpools\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pool\" }\n ,\"microsoft.storagepool/diskpools/iscsitargets\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pools iscsi target\" }\n ,\"microsoft.storagesync/storagesyncservices\": { \"SingularDisplayName\": \"Storage Sync Service\" }\n ,\"microsoft.storagetasks/storagetasks\": { \"SingularDisplayName\": \"Microsoft.StorageTasks storage task\" }\n ,\"microsoft.storsimple/managers\": { \"SingularDisplayName\": \"StorSimple device manager\" }\n ,\"microsoft.storsimple/managers/accesscontrolrecords\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers access control record\" }\n ,\"microsoft.storsimple/managers/bandwidthsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers bandwidth setting\" }\n ,\"microsoft.storsimple/managers/certificates\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers certificate\" }\n ,\"microsoft.storsimple/managers/devices\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers device\" }\n ,\"microsoft.storsimple/managers/devices/alertsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices alert setting\" }\n ,\"microsoft.storsimple/managers/devices/backuppolicies\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policy\" }\n ,\"microsoft.storsimple/managers/devices/backuppolicies/schedules\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policies schedule\" }\n ,\"microsoft.storsimple/managers/devices/backupschedulegroups\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup schedule group\" }\n ,\"microsoft.storsimple/managers/devices/chapsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices chap setting\" }\n ,\"microsoft.storsimple/managers/devices/fileservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileserver\" }\n ,\"microsoft.storsimple/managers/devices/fileservers/shares\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileservers share\" }\n ,\"microsoft.storsimple/managers/devices/iscsiservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiserver\" }\n ,\"microsoft.storsimple/managers/devices/iscsiservers/disks\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiservers disk\" }\n ,\"microsoft.storsimple/managers/devices/jobs\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices job\" }\n ,\"microsoft.storsimple/managers/devices/networksettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices network setting\" }\n ,\"microsoft.storsimple/managers/devices/securitysettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices security setting\" }\n ,\"microsoft.storsimple/managers/devices/timesettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices time setting\" }\n ,\"microsoft.storsimple/managers/devices/updatesummary\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices update summary\" }\n ,\"microsoft.storsimple/managers/devices/volumecontainers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume container\" }\n ,\"microsoft.storsimple/managers/devices/volumecontainers/volumes\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume containers volume\" }\n ,\"microsoft.storsimple/managers/encryptionsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers encryption setting\" }\n ,\"microsoft.storsimple/managers/extendedinformation\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers extended information\" }\n ,\"microsoft.storsimple/managers/storageaccountcredentials\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage account credential\" }\n ,\"microsoft.storsimple/managers/storagedomains\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage domain\" }\n ,\"microsoft.streamanalytics/clusters\": { \"SingularDisplayName\": \"Stream Analytics cluster\" }\n ,\"microsoft.streamanalytics/streamingjobs\": { \"SingularDisplayName\": \"Stream Analytics job\" }\n ,\"microsoft.subscription/aliases\": { \"SingularDisplayName\": \"Microsoft.Subscription aliase\" }\n ,\"microsoft.subscription/changetenantrequest\": { \"SingularDisplayName\": \"Microsoft.Subscription change tenant request\" }\n ,\"microsoft.subscription/policies\": { \"SingularDisplayName\": \"Microsoft.Subscription policy\" }\n ,\"microsoft.subscription/subscriptiondefinitions\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription definition\" }\n ,\"microsoft.subscription/subscriptionoperations\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription operation\" }\n ,\"microsoft.support/fileworkspaces\": { \"SingularDisplayName\": \"Microsoft.Support file workspace\" }\n ,\"microsoft.support/fileworkspaces/files\": { \"SingularDisplayName\": \"Microsoft.Support file workspaces file\" }\n ,\"microsoft.support/services\": { \"SingularDisplayName\": \"Microsoft.Support service\" }\n ,\"microsoft.support/services/problemclassifications\": { \"SingularDisplayName\": \"Microsoft.Support services problem classification\" }\n ,\"microsoft.support/supporttickets\": { \"SingularDisplayName\": \"Support Request\" }\n ,\"microsoft.sustainabilityservices/calculations\": { \"SingularDisplayName\": \"Project Sustainability Calculator\" }\n ,\"microsoft.symphony/instances\": { \"SingularDisplayName\": \"Microsoft.Symphony instance\" }\n ,\"microsoft.symphony/solutions\": { \"SingularDisplayName\": \"Microsoft.Symphony solution\" }\n ,\"microsoft.symphony/targets\": { \"SingularDisplayName\": \"Microsoft.Symphony target\" }\n ,\"microsoft.synapse/privatelinkhubs\": { \"SingularDisplayName\": \"Synapse private link hub\" }\n ,\"microsoft.synapse/workspaces\": { \"SingularDisplayName\": \"Synapse workspace\" }\n ,\"microsoft.synapse/workspaces/bigdatapools\": { \"SingularDisplayName\": \"Apache Spark pool\" }\n ,\"microsoft.synapse/workspaces/kustopools\": { \"SingularDisplayName\": \"Data Explorer pool\" }\n ,\"microsoft.synapse/workspaces/kustopools/databases\": { \"SingularDisplayName\": \"Data Explorer Database\" }\n ,\"microsoft.synapse/workspaces/scopepools\": { \"SingularDisplayName\": \"SCOPE pool\" }\n ,\"microsoft.synapse/workspaces/sqlpools\": { \"SingularDisplayName\": \"Dedicated SQL pool\" }\n ,\"microsoft.syntex/accounts\": { \"SingularDisplayName\": \"Microsoft.Syntex account\" }\n ,\"microsoft.syntex/documentprocessors\": { \"SingularDisplayName\": \"Microsoft.Syntex document processor\" }\n ,\"microsoft.test/healthdataaiservices\": { \"SingularDisplayName\": \"Azure Health Data and AI Services\" }\n ,\"microsoft.timeseriesinsights/environments\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environment\" }\n ,\"microsoft.timeseriesinsights/environments/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments access policy\" }\n ,\"microsoft.timeseriesinsights/environments/eventsources\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments event source\" }\n ,\"microsoft.timeseriesinsights/environments/referencedatasets\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments reference data set\" }\n ,\"microsoft.toolchainorchestrator/activations\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator activation\" }\n ,\"microsoft.toolchainorchestrator/campaigns\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaign\" }\n ,\"microsoft.toolchainorchestrator/campaigns/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaigns version\" }\n ,\"microsoft.toolchainorchestrator/catalogs\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalog\" }\n ,\"microsoft.toolchainorchestrator/catalogs/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalogs version\" }\n ,\"microsoft.toolchainorchestrator/diagnostics\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator diagnostic\" }\n ,\"microsoft.toolchainorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instance\" }\n ,\"microsoft.toolchainorchestrator/instances/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instances version\" }\n ,\"microsoft.toolchainorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solution\" }\n ,\"microsoft.toolchainorchestrator/solutions/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solutions version\" }\n ,\"microsoft.toolchainorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator target\" }\n ,\"microsoft.toolchainorchestrator/targets/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator targets version\" }\n ,\"microsoft.updatemanager/updaterules\": { \"SingularDisplayName\": \"Update Rule\" }\n ,\"microsoft.usagebilling/accounts\": { \"SingularDisplayName\": \"Microsoft.UsageBilling account\" }\n ,\"microsoft.usagebilling/accounts/dataexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts data export\" }\n ,\"microsoft.usagebilling/accounts/inputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts input\" }\n ,\"microsoft.usagebilling/accounts/metricexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts metric export\" }\n ,\"microsoft.usagebilling/accounts/pav2outputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pav2output\" }\n ,\"microsoft.usagebilling/accounts/pipelines\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipeline\" }\n ,\"microsoft.usagebilling/accounts/pipelines/outputselectors\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipelines output selector\" }\n ,\"microsoft.verifiedid/authorities\": { \"SingularDisplayName\": \"Microsoft.VerifiedId authority\" }\n ,\"microsoft.videoindexer/accounts\": { \"SingularDisplayName\": \"Azure AI Video Indexer\" }\n ,\"microsoft.virtualmachineimages/imagetemplates\": { \"SingularDisplayName\": \"Image template\" }\n ,\"microsoft.visualstudio/account\": { \"SingularDisplayName\": \"Azure DevOps organization\" }\n ,\"microsoft.vmware/resourcepools\": { \"SingularDisplayName\": \"Microsoft.VMware resource pool\" }\n ,\"microsoft.vmware/vcenters\": { \"SingularDisplayName\": \"Microsoft.VMware vcenter\" }\n ,\"microsoft.vmware/vcenters/inventoryitems\": { \"SingularDisplayName\": \"Microsoft.VMware vcenters inventory item\" }\n ,\"microsoft.vmware/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine\" }\n ,\"microsoft.vmware/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine template\" }\n ,\"microsoft.vmware/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.VMware virtual network\" }\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudnodes\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud node\" }\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudservices\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud service\" }\n ,\"microsoft.vmwarecloudsimple/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple virtual machine\" }\n ,\"microsoft.vnfmanager/devices\": { \"SingularDisplayName\": \"Microsoft.VnfManager device\" }\n ,\"microsoft.vnfmanager/vendors\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendor\" }\n ,\"microsoft.vnfmanager/vendors/skus\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendors SKU\" }\n ,\"microsoft.vnfmanager/vnfs\": { \"SingularDisplayName\": \"Microsoft.VnfManager vnf\" }\n ,\"microsoft.voiceservices/communicationsgateways\": { \"SingularDisplayName\": \"Communications Gateway\" }\n ,\"microsoft.voiceservices/communicationsgateways/testlines\": { \"SingularDisplayName\": \"Communications Gateway Test Line\" }\n ,\"microsoft.vsonline/accounts\": { \"SingularDisplayName\": \"Microsoft.VSOnline account\" }\n ,\"microsoft.vsonline/plans\": { \"SingularDisplayName\": \"Visual Studio Online Plan\" }\n ,\"microsoft.web/certificates\": { \"SingularDisplayName\": \"Microsoft.Web certificate\" }\n ,\"microsoft.web/connectiongateways\": { \"SingularDisplayName\": \"App Service on-premises data gateway\" }\n ,\"microsoft.web/connections\": { \"SingularDisplayName\": \"App Service API connection\" }\n ,\"microsoft.web/containerapps\": { \"SingularDisplayName\": \"Microsoft.Web container app\" }\n ,\"microsoft.web/containerapps/revisions\": { \"SingularDisplayName\": \"Microsoft.Web container apps revision\" }\n ,\"microsoft.web/customapis\": { \"SingularDisplayName\": \"Logic apps custom connector\" }\n ,\"microsoft.web/deletedsites\": { \"SingularDisplayName\": \"Microsoft.Web deleted site\" }\n ,\"microsoft.web/hostingenvironments\": { \"SingularDisplayName\": \"App Service Environment\" }\n ,\"microsoft.web/ishostingenvironmentnameavailable\": { \"SingularDisplayName\": \"Microsoft.Web ishostingenvironmentnameavailable\" }\n ,\"microsoft.web/kubeenvironments\": { \"SingularDisplayName\": \"App Service Kubernetes Environment\" }\n ,\"microsoft.web/logicappstemplate\": { \"SingularDisplayName\": \"Logic Apps Template\" }\n ,\"microsoft.web/publishingusers\": { \"SingularDisplayName\": \"Microsoft.Web publishing user\" }\n ,\"microsoft.web/serverfarms\": { \"SingularDisplayName\": \"App Service plan\" }\n ,\"microsoft.web/sites\": { \"SingularDisplayName\": \"App Service web app\" }\n ,\"microsoft.web/sites/slots\": { \"SingularDisplayName\": \"App Service deployment slot\" }\n ,\"microsoft.web/sourcecontrols\": { \"SingularDisplayName\": \"Microsoft.Web sourcecontrol\" }\n ,\"microsoft.web/staticsites\": { \"SingularDisplayName\": \"Static Web App\" }\n ,\"microsoft.weightsandbiases/instances\": { \"SingularDisplayName\": \"Azure Native Weights & Biases Cloud Service\" }\n ,\"microsoft.whiteboxcadlprovider/whiteboxresources\": { \"SingularDisplayName\": \"Microsoft.WhiteBoxCadlProvider white box resource\" }\n ,\"microsoft.windows365/cloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.Windows365 cloud pc delegated msi\" }\n ,\"microsoft.windowsesu/multipleactivationkeys\": { \"SingularDisplayName\": \"Microsoft.WindowsESU multiple activation key\" }\n ,\"microsoft.windowsiot/deviceservices\": { \"SingularDisplayName\": \"Microsoft.WindowsIoT device service\" }\n ,\"microsoft.windowspushnotificationservices/registrations\": { \"SingularDisplayName\": \"Windows Push Notification Service\" }\n ,\"microsoft.workloadmonitor/monitors\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitor\" }\n ,\"microsoft.workloadmonitor/monitors/history\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitors history\" }\n ,\"microsoft.workloads/configurationvalidationresults\": { \"SingularDisplayName\": \"Microsoft.Workloads configuration validation result\" }\n ,\"microsoft.workloads/connectors\": { \"SingularDisplayName\": \"Microsoft.Workloads connector\" }\n ,\"microsoft.workloads/connectors/acssbackups\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors acss backup\" }\n ,\"microsoft.workloads/connectors/amsinsights\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors ams insight\" }\n ,\"microsoft.workloads/connectors/sapvirtualinstancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors sap virtual instance monitor\" }\n ,\"microsoft.workloads/epicvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for Epic solution\" }\n ,\"microsoft.workloads/insights\": { \"SingularDisplayName\": \"Microsoft.Workloads insight\" }\n ,\"microsoft.workloads/instancegroupmonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance group monitor\" }\n ,\"microsoft.workloads/instancehealthdefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definition\" }\n ,\"microsoft.workloads/instancehealthdefinitions/signaldefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definitions signal definition\" }\n ,\"microsoft.workloads/instancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance monitor\" }\n ,\"microsoft.workloads/monitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP solutions\" }\n ,\"microsoft.workloads/oraclevirtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instance\" }\n ,\"microsoft.workloads/oraclevirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instances database instance\" }\n ,\"microsoft.workloads/phpworkloads\": { \"SingularDisplayName\": \"Microsoft.Workloads php workload\" }\n ,\"microsoft.workloads/phpworkloads/wordpressinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads php workloads wordpress instance\" }\n ,\"microsoft.workloads/sapdiscoverysites\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery site\" }\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instance\" }\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances/serverinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instances server instance\" }\n ,\"microsoft.workloads/sapvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/applicationinstances\": { \"SingularDisplayName\": \"App server instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/centralinstances\": { \"SingularDisplayName\": \"Central service instance for SAP solutions\" }\n ,\"microsoft.workloads/sapvirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Database for SAP solutions\" }\n ,\"microsoft.workloads/virtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instance\" }\n ,\"microsoft.workloads/virtualinstances/components\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instances component\" }\n ,\"microsoft.workloads/workloadinstance\": { \"SingularDisplayName\": \"My Resource\" }\n ,\"microsoft.zerotrustsegmentation/segmentationmanagers\": { \"SingularDisplayName\": \"Segmentation Manager\" }\n ,\"mongodb.atlas/organizations\": { \"SingularDisplayName\": \"MongoDB Atlas Organization\" }\n ,\"neon.postgres/organizations\": { \"SingularDisplayName\": \"Neon Serverless Postgres Organization\" }\n ,\"newrelic.observability/monitors\": { \"SingularDisplayName\": \"New Relic\" }\n ,\"nginx.nginxplus/nginxdeployments\": { \"SingularDisplayName\": \"NGINXaaS\" }\n ,\"oracle.database/autonomousdatabases\": { \"SingularDisplayName\": \"Autonomous Database\" }\n ,\"oracle.database/basedb\": { \"SingularDisplayName\": \"Autonomous Database\" }\n ,\"oracle.database/cloudexadatainfrastructures\": { \"SingularDisplayName\": \"Oracle Exadata Infrastructure\" }\n ,\"oracle.database/cloudvmclusters\": { \"SingularDisplayName\": \"Oracle Exadata VM Cluster\" }\n ,\"oracle.database/exadbvmclusters\": { \"SingularDisplayName\": \"Oracle Exascale VM Cluster\" }\n ,\"oracle.database/exascaledbstoragevaults\": { \"SingularDisplayName\": \"Oracle Exascale DB Storage Vault\" }\n ,\"oracle.database/networkanchors\": { \"SingularDisplayName\": \"Network Anchor\" }\n ,\"oracle.database/oraclesubscriptions\": { \"SingularDisplayName\": \"OracleSubscription\" }\n ,\"oracle.database/resourceanchors\": { \"SingularDisplayName\": \"Resource Anchor\" }\n ,\"paloaltonetworks.cloudngfw/firewalls\": { \"SingularDisplayName\": \"Cloud NGFW by Palo Alto Networks\" }\n ,\"paloaltonetworks.cloudngfw/globalrulestacks\": { \"SingularDisplayName\": \"Global Rulestack\" }\n ,\"paloaltonetworks.cloudngfw/localrulestacks\": { \"SingularDisplayName\": \"Local Rulestack for Cloud NGFW by Palo Alto Networks\" }\n ,\"pinecone.vectordb/organizations\": { \"SingularDisplayName\": \"Azure Native Pinecone Cloud Service\" }\n ,\"purestorage.block/reservations\": { \"SingularDisplayName\": \"Azure Native Pure Storage Cloud Service\" }\n ,\"purestorage.block/storagepools\": { \"SingularDisplayName\": \"Storage pool\" }\n ,\"purestorage.block/storagepools/avsstoragecontainers\": { \"SingularDisplayName\": \"PureStorage.Block storage pools avs storage container\" }\n })[tolower(id)]\n}\n", - "$fxv#4": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\n_resource_type_5(id: string) {\n dynamic({\n \"qumulo.qaas/storages\": { \"SingularDisplayName\": \"Qumulo.QaaS storage\" }\n ,\"qumulo.storage/filesystems\": { \"SingularDisplayName\": \"Azure Native Qumulo Scalable File Service\" }\n ,\"solarwinds.observability/organizations\": { \"SingularDisplayName\": \"SolarWinds Observability\" }\n ,\"splitio.experimentation/experimentationworkspaces\": { \"SingularDisplayName\": \"Split Experimentation Workspace\" }\n ,\"wandisco.fusion/migrators\": { \"SingularDisplayName\": \"LiveData Migrator\" }\n ,\"wandisco.fusion/migrators/datatransferagents\": { \"SingularDisplayName\": \"Data Transfer Agent\" }\n ,\"wandisco.fusion/migrators/exclusiontemplates\": { \"SingularDisplayName\": \"Exclusion\" }\n ,\"wandisco.fusion/migrators/livedatamigrations\": { \"SingularDisplayName\": \"Migration\" }\n ,\"wandisco.fusion/migrators/metadatamigrations\": { \"SingularDisplayName\": \"Metadata Migration\" }\n ,\"wandisco.fusion/migrators/metadatatargets\": { \"SingularDisplayName\": \"Metadata Target\" }\n ,\"wandisco.fusion/migrators/pathmappings\": { \"SingularDisplayName\": \"Path Mapping\" }\n ,\"wandisco.fusion/migrators/targets\": { \"SingularDisplayName\": \"Target\" }\n ,\"wandisco.fusion/migrators/verifications\": { \"SingularDisplayName\": \"Verification\" }\n })[tolower(id)]\n}\n", - "$fxv#5": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n// resource_type\n.create-or-alter function \nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData')\nresource_type(id: string) {\n coalesce(_resource_type_1(id), _resource_type_2(id), _resource_type_3(id), _resource_type_4(id), _resource_type_5(id))\n}\n", - "$fxv#6": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Common utility functions\n//\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\n//======================================================================================================================\n\n\n//===| Date functions |=================================================================================================\n\n// monthstring\n.create-or-alter function \nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \nmonthstring(['date']: datetime, length: int = 9)\n{\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\n}\n\n// datestring\n.create-or-alter function \nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n let month = (d: datetime) { monthstring(d, 3) };\n let endDate = iff(end == datetime('0001-01-01'), start, end);\n let sameDate = startofday(start) == startofday(endDate);\n let sameMonth = startofmonth(start) == startofmonth(endDate);\n let sameYear = startofyear(start) == startofyear(endDate);\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\n let currentYear = sameYear and startofyear(start) == startofyear(now());\n case(\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\n fullYear,\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\n // 1 full mo, same year | Mmm yyyy\n fullMonth and sameMonth and sameYear,\n strcat(month(start), ' ', getyear(start)),\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\n fullMonth and sameYear,\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\n fullMonth and not(sameYear),\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\n sameDate,\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\n not(fullMonth) and sameMonth and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\n not(fullMonth) and not(sameMonth) and sameYear,\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\n )\n}\n\n// daterange\n.create-or-alter function \nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\n{\n datestring(start, end)\n}\n\n// monthsago\n.create-or-alter function \nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\nmonthsago(months: int)\n{\n datetime_add('month', -months, startofmonth(now()))\n}\n\n\n//===| Number functions |===============================================================================================\n// NOTE: Must be defined before string converters\n\n// delta\n.create-or-alter function \nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \ndelta(oldval: double, newval: double)\n{\n (newval - todouble(oldval))/oldval\n}\n\n// percentOfTotal\n// NOTE: Must be before percent() function\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercentOfTotal(t: (Count: long), tot: long)\n{\n let total = todouble(tot);\n t \n | extend Percent = round(Count / total * 100, 3) \n | order by Count desc\n}\n\n// percent\n.create-or-alter function \nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \npercent(t: (Count: long))\n{\n let total = todouble(toscalar(t | summarize sum(Count)));\n percentOfTotal(t, total)\n}\n\n// plusminus\n.create-or-alter function \nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\nplusminus(val: string)\n{\n let neg = substring(val, 0, 1) == '-';\n iff(neg, val, strcat('+', val))\n}\n\n// updown\n.create-or-alter function \nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\nupdown(val: string)\n{\n // TODO: Handle 0\n let neg = substring(val, 0, 1) == '-';\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\n}\n\n\n//===| String functions |===============================================================================================\n\n// percentstring\n// NOTE: Must be defined before deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\npercentstring(num: double, total: double = 1.0, places: int = 9)\n{\n let value = 1.0 * num / total * 100;\n strcat(case(\n places != 9, round(value, places),\n value < 10, round(value, 2),\n round(value, 1)\n ), '%')\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// arraystring\n.create-or-alter function \nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\narraystring(arr: dynamic)\n{\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\n tostring(arr)\n , @'^\\[\"', '')\n , @'\"\\]$', '')\n , @'^, ', '')\n , @', $', '')\n , @'^\\[]$', '')\n , '\",\"', ', ')\n}\n\n// deltastring\n.create-or-alter function \nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\n{\n let d = delta(oldval, newval);\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\n}\n\n// diffstring\n.create-or-alter function \nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\ndiffstring(oldval: double, newval: double, places: int = 1)\n{\n plusminus(round(newval - oldval, places))\n}\n\n// numberstring\n.create-or-alter function \nwith (docstring = 'Convert a number to a string', folder = 'Common')\nnumberstring(num: double, abbrev: bool = true)\n{\n replace_regex(case(\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\n tostring(num)\n ), @'\\.0$', '')\n}\n\n\n//===| Other |==========================================================================================================\n\n// ifempty\n.create-or-alter function \nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\nifempty(val: dynamic, defaultVal: dynamic)\n{\n iff(isempty(val), defaultVal, val)\n}\n", - "$fxv#7": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Settings |=======================================================================================================\n\n.create-merge table HubSettingsLog (\n version: string,\n scopes: dynamic,\n retention: dynamic\n)\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// HubSettings function\n.create-or-alter function\nwith (docstring='Gets the latest version of hub settings.', folder='Settings')\nHubSettings()\n{\n HubSettingsLog\n | extend timestamp = ingestion_time()\n | summarize arg_max(timestamp, *)\n}\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// HubScopes function\n.create-or-alter function\nwith (docstring='Gets the currently configured scopes.', folder='Settings')\nHubScopes()\n{\n HubSettings\n | project scopes\n | mv-expand scopes\n}\n\n\n//===| Open data |======================================================================================================\n\n// PricingUnits -- Create table if it doesn't exist\n.create-merge table PricingUnits ( ignore: string )\n\n// PricingUnits -- Remove all columns\n.alter table PricingUnits ( ignore: string )\n\n// PricingUnits -- Redefine all columns to change types\n.alter table PricingUnits (\n x_PricingUnitDescription: string,\n x_PricingBlockSize: real,\n PricingUnit: string\n)\n\n// Regions\n.create-merge table Regions(\n ResourceLocation: string,\n RegionId: string,\n RegionName: string\n)\n\n// ResourceTypes\n.create-merge table ResourceTypes(\n x_ResourceType: string,\n SingularDisplayName: string,\n PluralDisplayName: string,\n LowerSingularDisplayName: string,\n LowerPluralDisplayName: string,\n IsPreview: bool,\n Description: string,\n IconUri: string\n)\n\n// Services\n.create-merge table Services(\n x_ConsumedService: string,\n x_ResourceType: string,\n ServiceName: string,\n ServiceCategory: string,\n ServiceSubcategory: string,\n PublisherName: string,\n x_PublisherCategory: string,\n x_Environment: string,\n x_ServiceModel: string\n)\n\n//----------------------------------------------------------------------------------------------------------------------\n\n// parse_resourceid\n.create-or-alter function\nwith (docstring = 'Parses an Azure resource ID to extract resource attributes like the name, type, resource group, and subaccount ID.', folder = 'Common')\nparse_resourceid(resourceId: string) {\n let ResourceId = tolower(resourceId);\n // let ResourceId = tolower('/providers/Microsoft.BillingBenefits/savingsPlanOrders/2d2e284b-0638-427e-b8c6-1b874d4f17c8/sp/xxx');\n let SubAccountId = tostring(extract('/subscriptions/[^/]+', 1, ResourceId));\n let x_ResourceGroupName = tostring(extract('/resourcegroups/[^/]+', 1, ResourceId));\n let providerPath = iff(ResourceId !contains '/providers/', '', split(iff(ResourceId startswith '/subscriptions/', strcat('/providers/microsoft.resources/', ResourceId), ResourceId), '/providers/')[-1]);\n let x_ResourceProvider = iff(isempty(providerPath), '', split(providerPath, '/')[0]);\n let tmp_ResourceProviderPath = iff(isempty(providerPath), '', substring(providerPath, strlen(x_ResourceProvider) + 1));\n let segments = split(tmp_ResourceProviderPath, '/');\n let ResourceName = trim(@'/+', replace_string(strcat_array(array_iff(\n dynamic([false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true]),\n segments, dynamic([])), '/'), '//', '/'));\n let x_ResourceTypePath = trim(@'/+', replace_string(strcat_array(array_iff(\n dynamic([true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]),\n segments, dynamic([])), '/'), '//', '/'));\n let xRT = iff(isempty(x_ResourceProvider) or isempty(x_ResourceTypePath), '', strcat(x_ResourceProvider, '/', x_ResourceTypePath));\n // TODO: Remove ResourceType in 0.9\n bag_pack('ResourceId', ResourceId, 'ResourceName', ResourceName, 'ResourceType', xRT, 'SubAccountId', SubAccountId, 'x_ResourceGroupName', x_ResourceGroupName, 'x_ResourceProvider', x_ResourceProvider, 'x_ResourceType', xRT)\n}\n", - "$fxv#8": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| ActualCosts |====================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_raw table -- Create the table if it doesn't exist\n.create-merge table ActualCosts_raw ( ignore: string )\n\n// ActualCosts_raw table -- Remove all columns to allow changing column types\n.alter table ActualCosts_raw ( ignore: string )\n\n// ActualCosts_raw table -- Redefine all columns\n.alter table ActualCosts_raw (\n AccountName: string,\n AccountOwnerId: string,\n AdditionalInfo: string,\n AvailabilityZone: string,\n BillingAccountId: string, \n BillingAccountName: string,\n BillingCurrency: string,\n BillingPeriodEndDate: datetime,\n BillingPeriodStartDate: datetime,\n BillingProfileId: string,\n BillingProfileName: string,\n ChargeType: string,\n ConsumedService: string,\n CostCenter: string,\n Cost: real,\n Date: datetime,\n EffectivePrice: real,\n Frequency: string,\n InvoiceSection: string,\n InvoiceSectionId: string,\n IsAzureCreditEligible: bool,\n MeterCategory: string,\n MeterId: string,\n MeterName: string,\n MeterRegion: string,\n MeterSubCategory: string,\n OfferId: string,\n PartNumber: string,\n PlanName: string,\n Product: string,\n ProductOrderId: string,\n ProductOrderName: string,\n PublisherName: string,\n PublisherType: string,\n Quantity: real,\n ReservationId: string,\n ReservationName: string,\n ResourceGroup: string,\n ResourceId: string,\n ResourceLocation: string,\n ResourceName: string,\n ServiceFamily: string,\n ServiceInfo1: string,\n ServiceInfo2: string,\n SubscriptionId: string,\n SubscriptionName: string,\n Tags: string,\n Term: string,\n UnitOfMeasure: string,\n UnitPrice: real\n)\n\n// ActualCosts_raw ingestion mapping\n.create-or-alter table ActualCosts_raw ingestion parquet mapping \"ActualCosts_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\n]\n```\n\n// ActualCosts_raw retention policy (clear historical data)\n.alter-merge table ActualCosts_raw policy retention softdelete = 0d recoverability = disabled\n\n// ActualCosts_raw retention policy (set the user-defined retention period)\n.alter-merge table ActualCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable ActualCosts_raw streaming ingestion (required for Fabric)\n.alter table ActualCosts_raw policy streamingingestion disable\n\n\n//===| AmortizedCosts |=================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_raw table -- Create the table if it doesn't exist\n.create-merge table AmortizedCosts_raw ( ignore: string )\n\n// AmortizedCosts_raw table -- Remove all columns to allow changing column types\n.alter table AmortizedCosts_raw ( ignore: string )\n\n// AmortizedCosts_raw table -- Redefine all columns\n.alter table AmortizedCosts_raw (\n AccountName: string,\n AccountOwnerId: string,\n AdditionalInfo: string,\n AvailabilityZone: string,\n BillingAccountId: string, \n BillingAccountName: string,\n BillingCurrency: string,\n BillingPeriodEndDate: datetime,\n BillingPeriodStartDate: datetime,\n BillingProfileId: string,\n BillingProfileName: string,\n ChargeType: string,\n ConsumedService: string,\n CostCenter: string,\n Cost: real,\n Date: datetime,\n EffectivePrice: real,\n Frequency: string,\n InvoiceSection: string,\n InvoiceSectionId: string,\n IsAzureCreditEligible: bool,\n MeterCategory: string,\n MeterId: string,\n MeterName: string,\n MeterRegion: string,\n MeterSubCategory: string,\n OfferId: string,\n PartNumber: string,\n PlanName: string,\n Product: string,\n ProductOrderId: string,\n ProductOrderName: string,\n PublisherName: string,\n PublisherType: string,\n Quantity: real,\n ReservationId: string,\n ReservationName: string,\n ResourceGroup: string,\n ResourceId: string,\n ResourceLocation: string,\n ResourceName: string,\n ServiceFamily: string,\n ServiceInfo1: string,\n ServiceInfo2: string,\n SubscriptionId: string,\n SubscriptionName: string,\n Tags: string,\n Term: string,\n UnitOfMeasure: string,\n UnitPrice: real\n)\n\n// AmortizedCosts_raw ingestion mapping\n.create-or-alter table AmortizedCosts_raw ingestion parquet mapping \"AmortizedCosts_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\n]\n```\n\n// AmortizedCosts_raw retention policy (clear historical data)\n.alter-merge table AmortizedCosts_raw policy retention softdelete = 0d recoverability = disabled\n\n// AmortizedCosts_raw retention policy (set the user-defined retention period)\n.alter-merge table AmortizedCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable AmortizedCosts_raw streaming ingestion (required for Fabric)\n.alter table AmortizedCosts_raw policy streamingingestion disable\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_raw table -- Create the table if it doesn't exist\n.create-merge table CommitmentDiscountUsage_raw ( ignore: string )\n\n// CommitmentDiscountUsage_raw table -- Remove all columns to allow changing column types\n.alter table CommitmentDiscountUsage_raw ( ignore: string )\n\n// CommitmentDiscountUsage_raw table -- Redefine all columns\n.alter table CommitmentDiscountUsage_raw (\n InstanceFlexibilityGroup: string,\n InstanceFlexibilityRatio: real,\n InstanceId: string,\n Kind: string,\n ReservationId: string,\n ReservationOrderId: string,\n ReservedHours: real,\n SkuName: string,\n TotalReservedQuantity: real,\n UsageDate: datetime,\n UsedHours: real,\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// CommitmentDiscountUsage_raw ingestion mapping\n.create-or-alter table CommitmentDiscountUsage_raw ingestion parquet mapping \"CommitmentDiscountUsage_raw_mapping\"\n```\n[\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\n { \"Column\": \"InstanceId\", \"Properties\": { \"Field\": \"InstanceId\" } },\n { \"Column\": \"Kind\", \"Properties\": { \"Field\": \"Kind\" } },\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\n { \"Column\": \"ReservedHours\", \"Properties\": { \"Field\": \"ReservedHours\" } },\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\n { \"Column\": \"TotalReservedQuantity\", \"Properties\": { \"Field\": \"TotalReservedQuantity\" } },\n { \"Column\": \"UsageDate\", \"Properties\": { \"Field\": \"UsageDate\" } },\n { \"Column\": \"UsedHours\", \"Properties\": { \"Field\": \"UsedHours\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// CommitmentDiscountUsage_raw retention policy (clear historical data)\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = 0d recoverability = disabled\n\n// CommitmentDiscountUsage_raw retention policy (set the user-defined retention period)\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable CommitmentDiscountUsage_raw streaming ingestion (required for Fabric)\n.alter table CommitmentDiscountUsage_raw policy streamingingestion disable\n\n\n//===| Costs |==========================================================================================================\n// Supported versions:\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n// - Tencent: 1.0 -- See https://www.tencentcloud.com/document/product/555/67495 / https://www.tencentcloud.com/document/product/555/67496\n// - Alibaba: 1.0 -- See https://www.alibabacloud.com/help/en/user-center/user-guide/export-alibaba-cloud-standard-billing-focus\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_raw table -- Create the table if it doesn't exist\n.create-merge table Costs_raw ( ignore: string )\n\n// Costs_raw table -- Remove all columns to allow changing column types\n.alter table Costs_raw ( ignore: string )\n\n// Costs_raw table -- Redefine all columns\n.alter table Costs_raw (\n AvailabilityZone: string, // FOCUS 0.5+\n BilledCost: real, // FOCUS 0.5+\n BillingAccountId: string, // FOCUS 0.5+\n BillingAccountName: string, // FOCUS 0.5+\n BillingAccountType: string, // Azure 1.0-preview(v1)+\n BillingCurrency: string, // FOCUS 0.5+\n BillingPeriodEnd: datetime, // FOCUS 0.5+\n BillingPeriodStart: datetime, // FOCUS 0.5+\n CapacityReservationId: string, // FOCUS 1.1+\n CapacityReservationStatus: string, // FOCUS 1.1+\n ChargeCategory: string, // FOCUS 1.0-preview+\n ChargeClass: string, // FOCUS 1.0+\n ChargeDescription: string, // FOCUS 1.0+\n ChargeFrequency: string, // FOCUS 1.0+\n ChargePeriodEnd: datetime, // FOCUS 0.5+\n ChargePeriodStart: datetime, // FOCUS 0.5+\n ChargeSubcategory: string, // FOCUS 1.0-preview only\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview+\n CommitmentDiscountId: string, // FOCUS 1.0-preview+\n CommitmentDiscountName: string, // FOCUS 1.0-preview+\n CommitmentDiscountQuantity: real, // FOCUS 1.1+\n CommitmentDiscountStatus: string, // FOCUS 1.0+\n CommitmentDiscountType: string, // FOCUS 1.0-preview+\n CommitmentDiscountUnit: string, // FOCUS 1.1+\n ConsumedQuantity: real, // FOCUS 1.0+\n ConsumedUnit: string, // FOCUS 1.0+\n ContractedCost: real, // FOCUS 1.0+\n ContractedUnitPrice: real, // FOCUS 1.0+\n EffectiveCost: real, // FOCUS 1.0-preview+\n InvoiceId: string, // FOCUS 1.2+\n InvoiceIssuerName: string, // FOCUS 0.5+\n ListCost: real, // FOCUS 1.0-preview+\n ListUnitPrice: real, // FOCUS 1.0-preview+\n PricingCategory: string, // FOCUS 1.0-preview+\n PricingCurrency: string, // FOCUS 1.2+\n PricingQuantity: real, // FOCUS 1.0-preview+\n PricingUnit: string, // FOCUS 1.0-preview+\n ProviderName: string, // FOCUS 0.5+\n PublisherName: string, // FOCUS 0.5+\n Region: string, // FOCUS 0.5-1.0-preview (deprecated)\n RegionId: string, // FOCUS 1.0+\n RegionName: string, // FOCUS 1.0+\n ResourceId: string, // FOCUS 0.5+\n ResourceName: string, // FOCUS 0.5+\n ResourceType: string, // FOCUS 1.0-preview+\n ServiceCategory: string, // FOCUS 0.5+\n ServiceName: string, // FOCUS 0.5+\n ServiceSubcategory: string, // FOCUS 1.1+\n SkuId: string, // FOCUS 1.0-preview+\n SkuMeter: string, // FOCUS 1.1+\n SkuPriceDetails: string, // FOCUS 1.1+\n SkuPriceId: string, // FOCUS 1.0-preview+\n SubAccountId: string, // FOCUS 0.5+\n SubAccountName: string, // FOCUS 0.5+\n SubAccountType: string, // Azure 1.0-preview(v1)+\n Tags: string, // FOCUS 1.0-preview+\n UsageAmount: real, // GCP Jan 2024 -- Removed Mar 2024 (UsageQuantity)\n UsageQuantity: real, // FOCUS 1.0-preview only\n UsageUnit: string, // FOCUS 1.0-preview only\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_AmortizationClass: string, // Azure 1.2-preview+\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingItemCode: string, // Alibaba 1.0+\n x_BillingItemName: string, // Alibaba 1.0+\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_CommodityCode: string, // Alibaba 1.0+\n x_CommodityName: string, // Alibaba 1.0+\n x_ComponentName: string, // Tencent 1.0+\n x_ComponentType: string, // Tencent 1.0+\n x_ContractedCostInUsd: real, // Azure 1.0+\n x_Cost: real, // GCP Jan 2024 -- Removed Jun 2024 (ContractedCost)\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: string, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_CostType: string, // GCP Jan 2024\n x_Credits: string, // GCP Jan 2024\n x_CurrencyConversionRate: real, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: string, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0+\n x_InstanceID: string, // Alibaba 1.0+\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_OnDemandCost: real, // Azure 1.0-preview(v1) only\n x_OnDemandCostInUsd: real, // Azure 1.0-preview(v1) only\n x_OnDemandUnitPrice: real, // Azure 1.0-preview(v1) only\n x_Operation: string, // AWS 1.0\n x_OwnerAccountID: string, // Tencent 1.0+\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\n x_PricingCurrency: string, // Azure 1.0-preview(v1)-1.0r2\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServiceModel: string, // Azure 1.2-preview+\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: string, // Azure 1.0-preview(v1)-1.2-preview\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterName: string, // Azure 1.0-preview(v1)-1.0r2\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuPlanName: string, // Azure 1.2-preview+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string, // Hubs v1_0+\n x_SubproductName: string, // Tencent 1.0+ // cSpell:ignore Subproduct\n x_UsageType: string // AWS 1.0\n)\n\n// Costs_raw ingestion mapping\n.create-or-alter table Costs_raw ingestion parquet mapping \"Costs_raw_mapping\"\n```\n[\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\n { \"Column\": \"BilledCost\", \"Properties\": { \"Field\": \"BilledCost\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingAccountType\", \"Properties\": { \"Field\": \"BillingAccountType\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingPeriodEnd\", \"Properties\": { \"Field\": \"BillingPeriodEnd\" } },\n { \"Column\": \"BillingPeriodStart\", \"Properties\": { \"Field\": \"BillingPeriodStart\" } },\n { \"Column\": \"CapacityReservationId\", \"Properties\": { \"Field\": \"CapacityReservationId\" } },\n { \"Column\": \"CapacityReservationStatus\", \"Properties\": { \"Field\": \"CapacityReservationStatus\" } },\n { \"Column\": \"ChargeCategory\", \"Properties\": { \"Field\": \"ChargeCategory\" } },\n { \"Column\": \"ChargeClass\", \"Properties\": { \"Field\": \"ChargeClass\" } },\n { \"Column\": \"ChargeDescription\", \"Properties\": { \"Field\": \"ChargeDescription\" } },\n { \"Column\": \"ChargeFrequency\", \"Properties\": { \"Field\": \"ChargeFrequency\" } },\n { \"Column\": \"ChargePeriodEnd\", \"Properties\": { \"Field\": \"ChargePeriodEnd\" } },\n { \"Column\": \"ChargePeriodStart\", \"Properties\": { \"Field\": \"ChargePeriodStart\" } },\n { \"Column\": \"ChargeSubcategory\", \"Properties\": { \"Field\": \"ChargeSubcategory\" } },\n { \"Column\": \"CommitmentDiscountCategory\", \"Properties\": { \"Field\": \"CommitmentDiscountCategory\" } },\n { \"Column\": \"CommitmentDiscountId\", \"Properties\": { \"Field\": \"CommitmentDiscountId\" } },\n { \"Column\": \"CommitmentDiscountName\", \"Properties\": { \"Field\": \"CommitmentDiscountName\" } },\n { \"Column\": \"CommitmentDiscountQuantity\", \"Properties\": { \"Field\": \"CommitmentDiscountQuantity\" } },\n { \"Column\": \"CommitmentDiscountStatus\", \"Properties\": { \"Field\": \"CommitmentDiscountStatus\" } },\n { \"Column\": \"CommitmentDiscountType\", \"Properties\": { \"Field\": \"CommitmentDiscountType\" } },\n { \"Column\": \"CommitmentDiscountUnit\", \"Properties\": { \"Field\": \"CommitmentDiscountUnit\" } },\n { \"Column\": \"ConsumedQuantity\", \"Properties\": { \"Field\": \"ConsumedQuantity\" } },\n { \"Column\": \"ConsumedUnit\", \"Properties\": { \"Field\": \"ConsumedUnit\" } },\n { \"Column\": \"ContractedCost\", \"Properties\": { \"Field\": \"ContractedCost\" } },\n { \"Column\": \"ContractedUnitPrice\", \"Properties\": { \"Field\": \"ContractedUnitPrice\" } },\n { \"Column\": \"EffectiveCost\", \"Properties\": { \"Field\": \"EffectiveCost\" } },\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\n { \"Column\": \"InvoiceIssuerName\", \"Properties\": { \"Field\": \"InvoiceIssuerName\" } },\n { \"Column\": \"ListCost\", \"Properties\": { \"Field\": \"ListCost\" } },\n { \"Column\": \"ListUnitPrice\", \"Properties\": { \"Field\": \"ListUnitPrice\" } },\n { \"Column\": \"PricingCategory\", \"Properties\": { \"Field\": \"PricingCategory\" } },\n { \"Column\": \"PricingCurrency\", \"Properties\": { \"Field\": \"PricingCurrency\" } },\n { \"Column\": \"PricingQuantity\", \"Properties\": { \"Field\": \"PricingQuantity\" } },\n { \"Column\": \"PricingUnit\", \"Properties\": { \"Field\": \"PricingUnit\" } },\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\n { \"Column\": \"RegionId\", \"Properties\": { \"Field\": \"RegionId\" } },\n { \"Column\": \"RegionName\", \"Properties\": { \"Field\": \"RegionName\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\n { \"Column\": \"ServiceCategory\", \"Properties\": { \"Field\": \"ServiceCategory\" } },\n { \"Column\": \"ServiceName\", \"Properties\": { \"Field\": \"ServiceName\" } },\n { \"Column\": \"ServiceSubcategory\", \"Properties\": { \"Field\": \"ServiceSubcategory\" } },\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\n { \"Column\": \"SkuMeter\", \"Properties\": { \"Field\": \"SkuMeter\" } },\n { \"Column\": \"SkuPriceDetails\", \"Properties\": { \"Field\": \"SkuPriceDetails\" } },\n { \"Column\": \"SkuPriceId\", \"Properties\": { \"Field\": \"SkuPriceId\" } },\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\n { \"Column\": \"SubAccountType\", \"Properties\": { \"Field\": \"SubAccountType\" } },\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\n { \"Column\": \"UsageAmount\", \"Properties\": { \"Field\": \"UsageAmount\" } },\n { \"Column\": \"UsageQuantity\", \"Properties\": { \"Field\": \"UsageQuantity\" } },\n { \"Column\": \"UsageUnit\", \"Properties\": { \"Field\": \"UsageUnit\" } },\n { \"Column\": \"x_AccountId\", \"Properties\": { \"Field\": \"x_AccountId\" } },\n { \"Column\": \"x_AccountName\", \"Properties\": { \"Field\": \"x_AccountName\" } },\n { \"Column\": \"x_AccountOwnerId\", \"Properties\": { \"Field\": \"x_AccountOwnerId\" } },\n { \"Column\": \"x_AmortizationClass\", \"Properties\": { \"Field\": \"x_AmortizationClass\" } },\n { \"Column\": \"x_BilledCostInUsd\", \"Properties\": { \"Field\": \"x_BilledCostInUsd\" } },\n { \"Column\": \"x_BilledUnitPrice\", \"Properties\": { \"Field\": \"x_BilledUnitPrice\" } },\n { \"Column\": \"x_BillingAccountId\", \"Properties\": { \"Field\": \"x_BillingAccountId\" } },\n { \"Column\": \"x_BillingAccountName\", \"Properties\": { \"Field\": \"x_BillingAccountName\" } },\n { \"Column\": \"x_BillingExchangeRate\", \"Properties\": { \"Field\": \"x_BillingExchangeRate\" } },\n { \"Column\": \"x_BillingExchangeRateDate\", \"Properties\": { \"Field\": \"x_BillingExchangeRateDate\" } },\n { \"Column\": \"x_BillingItemCode\", \"Properties\": { \"Field\": \"x_BillingItemCode\" } },\n { \"Column\": \"x_BillingItemName\", \"Properties\": { \"Field\": \"x_BillingItemName\" } },\n { \"Column\": \"x_BillingProfileId\", \"Properties\": { \"Field\": \"x_BillingProfileId\" } },\n { \"Column\": \"x_BillingProfileName\", \"Properties\": { \"Field\": \"x_BillingProfileName\" } },\n { \"Column\": \"x_ChargeId\", \"Properties\": { \"Field\": \"x_ChargeId\" } },\n { \"Column\": \"x_ContractedCostInUsd\", \"Properties\": { \"Field\": \"x_ContractedCostInUsd\" } },\n { \"Column\": \"x_CommodityCode\", \"Properties\": { \"Field\": \"x_CommodityCode\" } },\n { \"Column\": \"x_CommodityName\", \"Properties\": { \"Field\": \"x_CommodityName\" } },\n { \"Column\": \"x_ComponentName\", \"Properties\": { \"Field\": \"x_ComponentName\" } },\n { \"Column\": \"x_ComponentType\", \"Properties\": { \"Field\": \"x_ComponentType\" } },\n { \"Column\": \"x_Cost\", \"Properties\": { \"Field\": \"x_Cost\" } },\n { \"Column\": \"x_CostAllocationRuleName\", \"Properties\": { \"Field\": \"x_CostAllocationRuleName\" } },\n { \"Column\": \"x_CostCategories\", \"Properties\": { \"Field\": \"x_CostCategories\" } },\n { \"Column\": \"x_CostCenter\", \"Properties\": { \"Field\": \"x_CostCenter\" } },\n { \"Column\": \"x_Credits\", \"Properties\": { \"Field\": \"x_Credits\" } },\n { \"Column\": \"x_CostType\", \"Properties\": { \"Field\": \"x_CostType\" } },\n { \"Column\": \"x_CurrencyConversionRate\", \"Properties\": { \"Field\": \"x_CurrencyConversionRate\" } },\n { \"Column\": \"x_CustomerId\", \"Properties\": { \"Field\": \"x_CustomerId\" } },\n { \"Column\": \"x_CustomerName\", \"Properties\": { \"Field\": \"x_CustomerName\" } },\n { \"Column\": \"x_Discount\", \"Properties\": { \"Field\": \"x_Discount\" } },\n { \"Column\": \"x_EffectiveCostInUsd\", \"Properties\": { \"Field\": \"x_EffectiveCostInUsd\" } },\n { \"Column\": \"x_EffectiveUnitPrice\", \"Properties\": { \"Field\": \"x_EffectiveUnitPrice\" } },\n { \"Column\": \"x_ExportTime\", \"Properties\": { \"Field\": \"x_ExportTime\" } },\n { \"Column\": \"x_InstanceID\", \"Properties\": { \"Field\": \"x_InstanceID\" } },\n { \"Column\": \"x_InvoiceId\", \"Properties\": { \"Field\": \"x_InvoiceId\" } },\n { \"Column\": \"x_InvoiceIssuerId\", \"Properties\": { \"Field\": \"x_InvoiceIssuerId\" } },\n { \"Column\": \"x_InvoiceSectionId\", \"Properties\": { \"Field\": \"x_InvoiceSectionId\" } },\n { \"Column\": \"x_InvoiceSectionName\", \"Properties\": { \"Field\": \"x_InvoiceSectionName\" } },\n { \"Column\": \"x_ListCostInUsd\", \"Properties\": { \"Field\": \"x_ListCostInUsd\" } },\n { \"Column\": \"x_Location\", \"Properties\": { \"Field\": \"x_Location\" } },\n { \"Column\": \"x_OnDemandCost\", \"Properties\": { \"Field\": \"x_OnDemandCost\" } },\n { \"Column\": \"x_OnDemandCostInUsd\", \"Properties\": { \"Field\": \"x_OnDemandCostInUsd\" } },\n { \"Column\": \"x_OnDemandUnitPrice\", \"Properties\": { \"Field\": \"x_OnDemandUnitPrice\" } },\n { \"Column\": \"x_Operation\", \"Properties\": { \"Field\": \"x_Operation\" } },\n { \"Column\": \"x_OwnerAccountID\", \"Properties\": { \"Field\": \"x_OwnerAccountID\" } },\n { \"Column\": \"x_PartnerCreditApplied\", \"Properties\": { \"Field\": \"x_PartnerCreditApplied\" } },\n { \"Column\": \"x_PartnerCreditRate\", \"Properties\": { \"Field\": \"x_PartnerCreditRate\" } },\n { \"Column\": \"x_PricingBlockSize\", \"Properties\": { \"Field\": \"x_PricingBlockSize\" } },\n { \"Column\": \"x_PricingCurrency\", \"Properties\": { \"Field\": \"x_PricingCurrency\" } },\n { \"Column\": \"x_PricingSubcategory\", \"Properties\": { \"Field\": \"x_PricingSubcategory\" } },\n { \"Column\": \"x_PricingUnitDescription\", \"Properties\": { \"Field\": \"x_PricingUnitDescription\" } },\n { \"Column\": \"x_Project\", \"Properties\": { \"Field\": \"x_Project\" } },\n { \"Column\": \"x_PublisherCategory\", \"Properties\": { \"Field\": \"x_PublisherCategory\" } },\n { \"Column\": \"x_PublisherId\", \"Properties\": { \"Field\": \"x_PublisherId\" } },\n { \"Column\": \"x_ResellerId\", \"Properties\": { \"Field\": \"x_ResellerId\" } },\n { \"Column\": \"x_ResellerName\", \"Properties\": { \"Field\": \"x_ResellerName\" } },\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\n { \"Column\": \"x_ResourceType\", \"Properties\": { \"Field\": \"x_ResourceType\" } },\n { \"Column\": \"x_ServiceCode\", \"Properties\": { \"Field\": \"x_ServiceCode\" } },\n { \"Column\": \"x_ServiceId\", \"Properties\": { \"Field\": \"x_ServiceId\" } },\n { \"Column\": \"x_ServiceModel\", \"Properties\": { \"Field\": \"x_ServiceModel\" } },\n { \"Column\": \"x_ServicePeriodEnd\", \"Properties\": { \"Field\": \"x_ServicePeriodEnd\" } },\n { \"Column\": \"x_ServicePeriodStart\", \"Properties\": { \"Field\": \"x_ServicePeriodStart\" } },\n { \"Column\": \"x_SkuDescription\", \"Properties\": { \"Field\": \"x_SkuDescription\" } },\n { \"Column\": \"x_SkuDetails\", \"Properties\": { \"Field\": \"x_SkuDetails\" } },\n { \"Column\": \"x_SkuIsCreditEligible\", \"Properties\": { \"Field\": \"x_SkuIsCreditEligible\" } },\n { \"Column\": \"x_SkuMeterCategory\", \"Properties\": { \"Field\": \"x_SkuMeterCategory\" } },\n { \"Column\": \"x_SkuMeterId\", \"Properties\": { \"Field\": \"x_SkuMeterId\" } },\n { \"Column\": \"x_SkuMeterName\", \"Properties\": { \"Field\": \"x_SkuMeterName\" } },\n { \"Column\": \"x_SkuMeterSubcategory\", \"Properties\": { \"Field\": \"x_SkuMeterSubcategory\" } },\n { \"Column\": \"x_SkuOfferId\", \"Properties\": { \"Field\": \"x_SkuOfferId\" } },\n { \"Column\": \"x_SkuOrderId\", \"Properties\": { \"Field\": \"x_SkuOrderId\" } },\n { \"Column\": \"x_SkuOrderName\", \"Properties\": { \"Field\": \"x_SkuOrderName\" } },\n { \"Column\": \"x_SkuPartNumber\", \"Properties\": { \"Field\": \"x_SkuPartNumber\" } },\n { \"Column\": \"x_SkuPlanName\", \"Properties\": { \"Field\": \"x_SkuPlanName\" } },\n { \"Column\": \"x_SkuRegion\", \"Properties\": { \"Field\": \"x_SkuRegion\" } },\n { \"Column\": \"x_SkuServiceFamily\", \"Properties\": { \"Field\": \"x_SkuServiceFamily\" } },\n { \"Column\": \"x_SkuTerm\", \"Properties\": { \"Field\": \"x_SkuTerm\" } },\n { \"Column\": \"x_SkuTier\", \"Properties\": { \"Field\": \"x_SkuTier\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } },\n { \"Column\": \"x_SubproductName\", \"Properties\": { \"Field\": \"x_SubproductName\" } },\n { \"Column\": \"x_UsageType\", \"Properties\": { \"Field\": \"x_UsageType\" } }\n]\n```\n\n// Costs_raw retention policy (clear historical data)\n.alter-merge table Costs_raw policy retention softdelete = 0d recoverability = disabled\n\n// Costs_raw retention policy (set the user-defined retention period)\n.alter-merge table Costs_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Costs_raw streaming ingestion (required for Fabric)\n.alter table Costs_raw policy streamingingestion disable\n\n\n//===| Prices |=========================================================================================================\n// NOTE: Must be before cost details.\n//\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_raw table -- Create the table if it doesn't exist\n.create-merge table Prices_raw ( ignore: string )\n\n// Prices_raw table -- Remove all columns to allow changing column types\n.alter table Prices_raw ( ignore: string )\n\n// Prices_raw table -- Redefine all columns\n.alter table Prices_raw (\n BasePrice: real, // Azure EA + MCA\n BillingAccountId: string, // Azure MCA\n BillingAccountName: string, // Azure MCA\n BillingCurrency: string, // Azure MCA\n BillingProfileId: string, // Azure MCA\n BillingProfileName: string, // Azure MCA\n Currency: string, // Azure MCA\n CurrencyCode: string, // Azure EA\n EffectiveEndDate: datetime, // Azure MCA\n EffectiveStartDate: datetime, // Azure EA + MCA\n EnrollmentNumber: string, // Azure EA\n IncludedQuantity: real, // Azure EA\n MarketPrice: real, // Azure EA + MCA\n MeterCategory: string, // Azure EA + MCA\n MeterId: string, // Azure MCA\n MeterID: string, // Azure EA\n MeterName: string, // Azure EA + MCA\n MeterRegion: string, // Azure EA + MCA\n MeterSubCategory: string, // Azure EA + MCA\n MeterType: string, // Azure EA + MCA\n OfferID: string, // Azure EA\n PartNumber: string, // Azure EA\n PriceType: string, // Azure EA + MCA\n Product: string, // Azure EA + MCA\n ProductId: string, // Azure MCA\n ProductID: string, // Azure EA\n ServiceFamily: string, // Azure EA + MCA\n SkuId: string, // Azure MCA\n SkuID: string, // Azure EA\n Term: string, // Azure EA + MCA\n TierMinimumUnits: real, // Azure MCA\n UnitOfMeasure: string, // Azure EA + MCA\n UnitPrice: real, // Azure EA + MCA\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Prices_raw ingestion mapping\n.create-or-alter table Prices_raw ingestion parquet mapping \"Prices_raw_mapping\"\n```\n[\n { \"Column\": \"BasePrice\", \"Properties\": { \"Field\": \"BasePrice\" } },\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\n { \"Column\": \"CurrencyCode\", \"Properties\": { \"Field\": \"CurrencyCode\" } },\n { \"Column\": \"EffectiveEndDate\", \"Properties\": { \"Field\": \"EffectiveEndDate\" } },\n { \"Column\": \"EffectiveStartDate\", \"Properties\": { \"Field\": \"EffectiveStartDate\" } },\n { \"Column\": \"EnrollmentNumber\", \"Properties\": { \"Field\": \"EnrollmentNumber\" } },\n { \"Column\": \"IncludedQuantity\", \"Properties\": { \"Field\": \"IncludedQuantity\" } },\n { \"Column\": \"MarketPrice\", \"Properties\": { \"Field\": \"MarketPrice\" } },\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"MeterID\", \"Properties\": { \"Field\": \"MeterID\" } },\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\n { \"Column\": \"MeterType\", \"Properties\": { \"Field\": \"MeterType\" } },\n { \"Column\": \"OfferID\", \"Properties\": { \"Field\": \"OfferID\" } },\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\n { \"Column\": \"PriceType\", \"Properties\": { \"Field\": \"PriceType\" } },\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\n { \"Column\": \"ProductId\", \"Properties\": { \"Field\": \"ProductId\" } },\n { \"Column\": \"ProductID\", \"Properties\": { \"Field\": \"ProductID\" } },\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\n { \"Column\": \"SkuID\", \"Properties\": { \"Field\": \"SkuID\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"TierMinimumUnits\", \"Properties\": { \"Field\": \"TierMinimumUnits\" } },\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Prices_raw retention policy (clear historical data)\n.alter-merge table Prices_raw policy retention softdelete = 0d recoverability = disabled\n\n// Prices_raw retention policy (set the user-defined retention period)\n.alter-merge table Prices_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Prices_raw streaming ingestion (required for Fabric)\n.alter table Prices_raw policy streamingingestion disable\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_raw table -- Create the table if it doesn't exist\n.create-merge table Recommendations_raw ( ignore: string )\n\n// Recommendations_raw table -- Remove all columns to allow changing column types\n.alter table Recommendations_raw ( ignore: string )\n\n// Recommendations_raw table -- Redefine all columns\n.alter table Recommendations_raw (\n CostWithNoReservedInstances: real, // MS CM EA resv reco 2024-05-01\n CostWithNoReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n FirstUsageDate: datetime, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n InstanceFlexibilityGroup: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n InstanceFlexibilityRatio: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n Location: string, // MS CM EA+MCA resv reco 2024-05-01\n LookBackPeriod: string, // MS CM EA+MCA resv reco 2024-05-01\n MeterId: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n NetSavings: real, // MS CM EA resv reco 2024-05-01\n NetSavingsJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n NormalizedSize: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n ProviderName: string, // Hubs v1_2\n RecommendedQuantity: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n RecommendedQuantityNormalized: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n ResourceId: string, // Hubs v1_2\n ResourceName: string, // Hubs v1_2\n ResourceType: string, // Hubs v1_2, MS CM EA+MCA resv reco 2024-05-01\n Scope: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n SKU: string, // MS CM EA resv reco 2024-05-01\n SkuName: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces\n SkuProperties: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\n SubAccountId: string, // Hubs v1_2\n SubAccountName: string, // Hubs v1_2\n SubscriptionId: string, // MS CM EA+MCA resv reco 2024-05-01\n Term: string, // MS CM EA+MCA resv reco 2024-05-01\n TotalCostWithReservedInstances: real, // MS CM EA resv reco 2024-05-01\n TotalCostWithReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\n x_EffectiveCostAfter: real, // Hubs v1_2\n x_EffectiveCostBefore: real, // Hubs v1_2\n x_EffectiveCostSavings: real, // Hubs v1_2\n x_RecommendationCategory: string, // Hubs v1_2\n x_RecommendationDate: datetime, // Hubs v1_2\n x_RecommendationDescription: string, // Hubs v1_2\n x_RecommendationDetails: dynamic, // Hubs v1_2\n x_RecommendationId: string, // Hubs v1_2\n x_ResourceGroupName: string, // Hubs v1_2\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Recommendations_raw ingestion mapping\n.create-or-alter table Recommendations_raw ingestion parquet mapping \"Recommendations_raw_mapping\"\n```\n[\n { \"Column\": \"CostWithNoReservedInstances\", \"Properties\": { \"Field\": \"CostWithNoReservedInstances\" } },\n { \"Column\": \"CostWithNoReservedInstancesJson\", \"Properties\": { \"Field\": \"CostWithNoReservedInstancesJson\" } },\n { \"Column\": \"FirstUsageDate\", \"Properties\": { \"Field\": \"FirstUsageDate\" } },\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\n { \"Column\": \"Location\", \"Properties\": { \"Field\": \"Location\" } },\n { \"Column\": \"LookBackPeriod\", \"Properties\": { \"Field\": \"LookBackPeriod\" } },\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\n { \"Column\": \"NetSavings\", \"Properties\": { \"Field\": \"NetSavings\" } },\n { \"Column\": \"NetSavingsJson\", \"Properties\": { \"Field\": \"NetSavingsJson\" } },\n { \"Column\": \"NormalizedSize\", \"Properties\": { \"Field\": \"NormalizedSize\" } },\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\n { \"Column\": \"RecommendedQuantity\", \"Properties\": { \"Field\": \"RecommendedQuantity\" } },\n { \"Column\": \"RecommendedQuantityNormalized\", \"Properties\": { \"Field\": \"RecommendedQuantityNormalized\" } },\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\n { \"Column\": \"Scope\", \"Properties\": { \"Field\": \"Scope\" } },\n { \"Column\": \"SKU\", \"Properties\": { \"Field\": \"SKU\" } },\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\n { \"Column\": \"SkuProperties\", \"Properties\": { \"Field\": \"SkuProperties\" } },\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"TotalCostWithReservedInstances\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstances\" } },\n { \"Column\": \"TotalCostWithReservedInstancesJson\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstancesJson\" } },\n { \"Column\": \"x_EffectiveCostAfter\", \"Properties\": { \"Field\": \"x_EffectiveCostAfter\" } },\n { \"Column\": \"x_EffectiveCostBefore\", \"Properties\": { \"Field\": \"x_EffectiveCostBefore\" } },\n { \"Column\": \"x_EffectiveCostSavings\", \"Properties\": { \"Field\": \"x_EffectiveCostSavings\" } },\n { \"Column\": \"x_RecommendationCategory\", \"Properties\": { \"Field\": \"x_RecommendationCategory\" } },\n { \"Column\": \"x_RecommendationDate\", \"Properties\": { \"Field\": \"x_RecommendationDate\" } },\n { \"Column\": \"x_RecommendationDescription\", \"Properties\": { \"Field\": \"x_RecommendationDescription\" } },\n { \"Column\": \"x_RecommendationDetails\", \"Properties\": { \"Field\": \"x_RecommendationDetails\" } },\n { \"Column\": \"x_RecommendationId\", \"Properties\": { \"Field\": \"x_RecommendationId\" } },\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Recommendations_raw retention policy (clear historical data)\n.alter-merge table Recommendations_raw policy retention softdelete = 0d recoverability = disabled\n\n// Recommendations_raw retention policy (set the user-defined retention period)\n.alter-merge table Recommendations_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Recommendations_raw streaming ingestion (required for Fabric)\n.alter table Recommendations_raw policy streamingingestion disable\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_raw table -- Create the table if it doesn't exist\n.create-merge table Transactions_raw ( ignore: string )\n\n// Transactions_raw table -- Remove all columns to allow changing column types\n.alter table Transactions_raw ( ignore: string )\n\n// Transactions_raw table -- Redefine all columns\n.alter table Transactions_raw (\n AccountName: string, // MS CM EA resv trans 2023-05-01\n AccountOwnerEmail: string, // MS CM EA resv trans 2023-05-01\n Amount: real, // MS CM EA+MCA resv trans 2023-05-01\n ArmSkuName: string, // MS CM EA+MCA resv trans 2023-05-01\n BillingFrequency: string, // MS CM EA+MCA resv trans 2023-05-01\n BillingMonth: string, // MS CM EA resv trans 2023-05-01\n BillingProfileId: string, // MS CM MCA resv trans 2023-05-01\n BillingProfileName: string, // MS CM MCA resv trans 2023-05-01\n CostCenter: string, // MS CM EA resv trans 2023-05-01\n Currency: string, // MS CM EA+MCA resv trans 2023-05-01\n CurrentEnrollmentId: string, // MS CM EA resv trans 2023-05-01\n DepartmentName: string, // MS CM EA resv trans 2023-05-01\n Description: string, // MS CM EA+MCA resv trans 2023-05-01\n EventDate: datetime, // MS CM EA+MCA resv trans 2023-05-01\n EventType: string, // MS CM EA+MCA resv trans 2023-05-01\n Invoice: string, // MS CM EA+MCA resv trans 2023-05-01\n InvoiceId: string, // MS CM EA+MCA resv trans 2023-05-01\n InvoiceSectionId: string, // MS CM MCA resv trans 2023-05-01\n InvoiceSectionName: string, // MS CM MCA resv trans 2023-05-01\n MonetaryCommitment: real, // MS CM EA resv trans 2023-05-01\n Overage: real, // MS CM EA resv trans 2023-05-01\n PurchasingEnrollment: string, // MS CM EA resv trans 2023-05-01\n PurchasingSubscriptionGuid: string, // MS CM EA+MCA resv trans 2023-05-01\n PurchasingSubscriptionName: string, // MS CM EA+MCA resv trans 2023-05-01\n Quantity: real, // MS CM EA+MCA resv trans 2023-05-01\n Region: string, // MS CM EA+MCA resv trans 2023-05-01\n ReservationOrderId: string, // MS CM EA+MCA resv trans 2023-05-01\n ReservationOrderName: string, // MS CM EA+MCA resv trans 2023-05-01\n Term: string, // MS CM EA+MCA resv trans 2023-05-01\n x_SourceName: string, // Hubs v1_0+\n x_SourceProvider: string, // Hubs v1_0+\n x_SourceType: string, // Hubs v1_0+\n x_SourceVersion: string // Hubs v1_0+\n)\n\n// Transactions_raw ingestion mapping\n.create-or-alter table Transactions_raw ingestion parquet mapping \"Transactions_raw_mapping\"\n```\n[\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\n { \"Column\": \"AccountOwnerEmail\", \"Properties\": { \"Field\": \"AccountOwnerEmail\" } },\n { \"Column\": \"Amount\", \"Properties\": { \"Field\": \"Amount\" } },\n { \"Column\": \"ArmSkuName\", \"Properties\": { \"Field\": \"ArmSkuName\" } },\n { \"Column\": \"BillingFrequency\", \"Properties\": { \"Field\": \"BillingFrequency\" } },\n { \"Column\": \"BillingMonth\", \"Properties\": { \"Field\": \"BillingMonth\" } },\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\n { \"Column\": \"CurrentEnrollmentId\", \"Properties\": { \"Field\": \"CurrentEnrollmentId\" } },\n { \"Column\": \"DepartmentName\", \"Properties\": { \"Field\": \"DepartmentName\" } },\n { \"Column\": \"Description\", \"Properties\": { \"Field\": \"Description\" } },\n { \"Column\": \"EventDate\", \"Properties\": { \"Field\": \"EventDate\" } },\n { \"Column\": \"EventType\", \"Properties\": { \"Field\": \"EventType\" } },\n { \"Column\": \"Invoice\", \"Properties\": { \"Field\": \"Invoice\" } },\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\n { \"Column\": \"InvoiceSectionName\", \"Properties\": { \"Field\": \"InvoiceSectionName\" } },\n { \"Column\": \"MonetaryCommitment\", \"Properties\": { \"Field\": \"MonetaryCommitment\" } },\n { \"Column\": \"Overage\", \"Properties\": { \"Field\": \"Overage\" } },\n { \"Column\": \"PurchasingEnrollment\", \"Properties\": { \"Field\": \"PurchasingEnrollment\" } },\n { \"Column\": \"PurchasingSubscriptionGuid\", \"Properties\": { \"Field\": \"PurchasingSubscriptionGuid\" } },\n { \"Column\": \"PurchasingSubscriptionName\", \"Properties\": { \"Field\": \"PurchasingSubscriptionName\" } },\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\n { \"Column\": \"ReservationOrderName\", \"Properties\": { \"Field\": \"ReservationOrderName\" } },\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\n]\n```\n\n// Transactions_raw retention policy (clear historical data)\n.alter-merge table Transactions_raw policy retention softdelete = 0d recoverability = disabled\n\n// Transactions_raw retention policy (set the user-defined retention period)\n.alter-merge table Transactions_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\n\n// Disable Transactions_raw streaming ingestion (required for Fabric)\n.alter table Transactions_raw policy streamingingestion disable\n\n", - "$fxv#9": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n//======================================================================================================================\n// Ingestion database\n// Used for data ingestion, normalization, and cleansing.\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\n//======================================================================================================================\n\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\n\n//===| Prices |=========================================================================================================\n// Supported versions:\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\n//======================================================================================================================\n\n// Prices_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All prices transformed to FOCUS 1.0. Use Prices_transform_v1_2() instead.', folder='Prices')\nPrices_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n let prices = materialize(\n Prices_raw\n //\n // Change real to decimal\n | extend\n BasePrice = todecimal(BasePrice),\n IncludedQuantity = todecimal(IncludedQuantity),\n MarketPrice = todecimal(MarketPrice),\n TierMinimumUnits = todecimal(TierMinimumUnits),\n UnitPrice = todecimal(UnitPrice)\n //\n | extend x_SkuId = coalesce(SkuId, SkuID)\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\n | extend x_SkuTerm = isoMonths(Term)\n | project-rename\n x_BaseUnitPrice = BasePrice,\n x_EffectivePeriodEnd = EffectiveEndDate,\n x_EffectivePeriodStart = EffectiveStartDate,\n x_PricingUnitDescription = UnitOfMeasure,\n x_SkuIncludedQuantity = IncludedQuantity,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuMeterType = MeterType,\n x_SkuOfferId = OfferID,\n x_SkuPartNumber = PartNumber,\n x_SkuPriceType = PriceType,\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTier = TierMinimumUnits\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, todecimal('')) // UnitPrice for savings plan is not the on-demand unit price\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, todecimal('')) // MarketPrice for savings plan is not the list price\n | extend ChargeCategory = case(\n x_SkuPriceType == 'Consumption', 'Usage',\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\n ''\n )\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\n //\n // Get latest ingested row based on the unique ID\n | extend x_IngestionTime = ingestion_time()\n );\n //\n // Meters for reservations and savings plans to identify commitment eligibility\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\n //\n // Copy list/base/contracted prices from on-demand SKUs\n prices\n | where x_SkuPriceType == 'SavingsPlan'\n // If we use join, specify the shuffle key\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\n //\n // Calculate commitment discount elgibility\n // TODO: Would a join be faster?\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\n //\n // Add PricingUnit and x_PricingBlockSize\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\n | lookup kind=leftouter (PricingUnits | extend x_PricingBlockSize = todecimal(x_PricingBlockSize)) on x_PricingUnitDescription\n //\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, todecimal('')) // Savings plan prices are for the effective price, not the contracted price\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\n | project\n BillingAccountId = tolower(case(\n BillingProfileId startswith '/', BillingProfileId,\n BillingAccountId startswith '/', BillingAccountId,\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\n )),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\n ChargeCategory,\n CommitmentDiscountCategory = case(\n x_SkuPriceType == 'ReservedInstance', 'Usage',\n x_SkuPriceType == 'SavingsPlan', 'Spend',\n ''\n ),\n CommitmentDiscountType = case(\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\n ''\n ),\n ContractedUnitPrice,\n ListUnitPrice,\n PricingCategory = case(\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed',\n ''\n ),\n PricingUnit,\n SkuId = coalesce(ProductId, ProductID),\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\n SkuPriceIdv2,\n x_BaseUnitPrice,\n x_BillingAccountAgreement = case(\n strlen(x_BillingAccountId) > 32, 'MCA',\n strlen(x_BillingAccountId) < 32, 'EA',\n 'Unknown'\n ),\n x_BillingAccountId,\n x_BillingProfileId,\n x_CommitmentDiscountSpendEligibility,\n x_CommitmentDiscountUsageEligibility,\n x_ContractedUnitPriceDiscount,\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\n x_EffectivePeriodStart,\n x_EffectiveUnitPrice,\n x_EffectiveUnitPriceDiscount,\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\n x_IngestionTime,\n x_PricingBlockSize,\n x_PricingCurrency = coalesce(Currency, CurrencyCode), // CurrencyCode last as a fallback only\n x_PricingSubcategory = case(\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\n x_SkuPriceType == 'Consumption', 'Standard',\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\n ''\n ),\n x_PricingUnitDescription,\n x_SkuDescription = Product,\n x_SkuId,\n x_SkuIncludedQuantity,\n x_SkuMeterCategory,\n x_SkuMeterId,\n x_SkuMeterName,\n x_SkuMeterSubcategory,\n x_SkuMeterType,\n x_SkuPriceType,\n x_SkuProductId,\n x_SkuRegion,\n x_SkuServiceFamily,\n x_SkuOfferId,\n x_SkuPartNumber,\n x_SkuTerm,\n x_SkuTier,\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\n x_TotalUnitPriceDiscount,\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\n}\n\n// Prices_final_v1_0 table\n.create-merge table Prices_final_v1_0 (\n BillingAccountId: string,\n BillingAccountName: string,\n BillingCurrency: string,\n ChargeCategory: string,\n CommitmentDiscountCategory: string,\n CommitmentDiscountType: string,\n ContractedUnitPrice: decimal,\n ListUnitPrice: decimal,\n PricingCategory: string,\n PricingUnit: string,\n SkuId: string,\n SkuPriceId: string,\n SkuPriceIdv2: string, // Hubs add-on\n x_BaseUnitPrice: decimal, // Azure\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure MCA\n x_BillingProfileId: string, // Azure MCA\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\n x_ContractedUnitPriceDiscount: decimal, // Hubs add-on\n x_ContractedUnitPriceDiscountPercent: decimal, // Hubs add-on\n x_EffectivePeriodEnd: datetime, // Azure\n x_EffectivePeriodStart: datetime, // Azure\n x_EffectiveUnitPrice: decimal, // Azure\n x_EffectiveUnitPriceDiscount: decimal, // Hubs add-on\n x_EffectiveUnitPriceDiscountPercent: decimal, // Hubs add-on\n x_IngestionTime: datetime, // Hubs add-on\n x_PricingBlockSize: decimal, // Hubs add-on\n x_PricingCurrency: string, // Azure\n x_PricingSubcategory: string, // Hubs add-on\n x_PricingUnitDescription: string, // Azure\n x_SkuDescription: string, // Azure\n x_SkuId: string, // Azure\n x_SkuIncludedQuantity: decimal, // Azure EA\n x_SkuMeterCategory: string, // Azure\n x_SkuMeterId: string, // Azure\n x_SkuMeterName: string, // Azure\n x_SkuMeterSubcategory: string, // Azure\n x_SkuMeterType: string, // Azure\n x_SkuPriceType: string, // Azure\n x_SkuProductId: string, // Azure\n x_SkuRegion: string, // Azure\n x_SkuServiceFamily: string, // Azure\n x_SkuOfferId: string, // Azure EA\n x_SkuPartNumber: string, // Azure EA\n x_SkuTerm: int, // Azure\n x_SkuTier: decimal, // Azure MCA\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_TotalUnitPriceDiscount: decimal, // Hubs add-on\n x_TotalUnitPriceDiscountPercent: decimal // Hubs add-on\n)\n\n// Update policy for Prices_raw -> Prices_final_v1_0\n.alter table Prices_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Prices_raw\",\n \"Query\": \"Prices_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Cost and usage |=================================================================================================\n// Supported versions:\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\n// See also:\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\n//\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\n//======================================================================================================================\n\n// Costs_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All costs transformed to FOCUS 1.0. Use Costs_transform_v1_2() instead.', folder='Costs')\nCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n Costs_raw\n //\n // Change real to decimal\n | extend\n BilledCost = todecimal(BilledCost),\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\n ConsumedQuantity = todecimal(ConsumedQuantity),\n ContractedCost = todecimal(ContractedCost),\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\n EffectiveCost = todecimal(EffectiveCost),\n ListCost = todecimal(ListCost),\n ListUnitPrice = todecimal(ListUnitPrice),\n PricingQuantity = todecimal(PricingQuantity),\n UsageAmount = todecimal(UsageAmount),\n UsageQuantity = todecimal(UsageQuantity),\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\n x_Cost = todecimal(x_Cost),\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\n x_OnDemandCost = todecimal(x_OnDemandCost),\n x_OnDemandCostInUsd = todecimal(x_OnDemandCostInUsd),\n x_OnDemandUnitPrice = todecimal(x_OnDemandUnitPrice),\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\n //\n // Dedupe rows\n | extend x_IngestionTime = ingestion_time()\n | extend x_ChargeId = ''\n // TODO: Consider adding a unique charge ID per row\n // hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // // 1. Resource hierarchy (including resource name), highest to lowest\n // BillingAccountId,\n // x_InvoiceSectionId,\n // x_AccountOwnerId,\n // SubAccountId,\n // x_ResourceGroupName,\n // ResourceName,\n // // 2. Resource details\n // ResourceId,\n // RegionId,\n // Tags,\n // CommitmentDiscountId,\n // x_CostCenter,\n // // 4. Meter details\n // SkuPriceId,\n // x_SkuMeterId,\n // x_SkuPartNumber,\n // x_SkuOfferId,\n // x_SkuDetails,\n // // 5. Date\n // ChargePeriodStart\n // ))\n //\n // Identify data quality issues\n | extend x_SourceChanges = trim_end(',', strcat(\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\n 'XEffectiveUnitPriceRoundingError,', ''),\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\n ))\n //\n // Fix columns needed in other changes\n | extend ProviderName = case(\n isnotempty(ProviderName), ProviderName,\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\n ''\n )\n //\n // Identify source\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\n ''\n ))\n // Append version check error code\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\n )\n //\n // Fix quantities\n | extend PricingQuantity = case(\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\n PricingQuantity\n )\n | extend ConsumedQuantity = case(\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, decimal(1)),\n ConsumedQuantity\n )\n //\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\n and (ListUnitPrice == 0 or ContractedUnitPrice == 0)\n and x_EffectiveUnitPrice != 0\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\n | as allCosts\n | where tmp_MissingPrices\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | as costsWithMissingPrices\n | join kind=leftouter (\n Prices_final_v1_0\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\n ) on tmp_ReservationPriceLookupKey\n //\n // Select the best price to use for each row\n // TODO: Save values before changing -- | extend x_old_ContractedUnitPrice = ContractedUnitPrice, x_old_EffectiveUnitPrice = x_EffectiveUnitPrice, x_old_ListUnitPrice = ListUnitPrice, x_old_ListCost = ListCost, x_old_ContractedCost = ContractedCost\n | extend x_EffectiveUnitPrice = case(\n // If price is a rounding error away from the billed price, use the billed price\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\n // If price is a rounding error away from the contracted price, use the contracted price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\n x_EffectiveUnitPrice\n )\n | extend ContractedUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\n x_EffectiveUnitPrice\n )\n | extend ListUnitPrice = case(\n // If price is already correct, keep that\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\n // If both prices use the same scale, use the new one\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\n // If prices are the same unit but not the same scale, use the new one but correct the scale\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\n // Otherwise, assume the contracted price is the same as list price to support aggregations\n ContractedUnitPrice\n )\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\n | extend ContractedCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\n // ContractedCost is 0 in all other scenarios...\n // If 0 and there's a billed cost and prices are the same, use BilledCost\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume EffectiveCost\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\n // Fall back to the original value for any unhandled scenarios\n ContractedCost\n )\n | extend ListCost = case(\n // If not set or there's no cost, keep the original value\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\n // ListCost is 0 in all other scenarios...\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\n // If 0 and there's a price, calculate the cost based on the price\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\n // If 0 and there's no price, assume ContractedCost\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\n // Fall back to the original value for any unhandled scenarios\n ListCost\n )\n // Merge the rest of the unmodified cost records and remove excess columns\n | union (allCosts | where not(tmp_MissingPrices))\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\n //\n // BUG: Fix ContractedCost that has bad values\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\n //\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), todecimal(''))\n | extend ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\n //\n // Convert IDs to lowercase for consistency\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\n //\n // BUG: Remove EffectiveCost for commitment discount purchases\n | extend EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), EffectiveCost)\n | extend x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), x_EffectiveCostInUsd)\n //\n // Clean up resource columns\n | extend ResourceId = case(\n isnotempty(ResourceId), ResourceId,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\n ResourceId)\n | extend ResourceName = tolower(case(\n isnotempty(ResourceName), ResourceName,\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\n ResourceName))\n | extend x_ResourceType = case(\n isnotempty(x_ResourceType), x_ResourceType,\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\n x_ResourceType)\n | extend ResourceType = case(\n // Use existing resource type display name unless it's an internal resource type ID\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\n // Use CommitmentDiscountType for commitment discount purchases\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\n // Look up display name from internal type\n isnotempty(x_ResourceType), coalesce(resource_type(x_ResourceType).SingularDisplayName, ResourceType, x_ResourceType),\n ResourceType)\n //\n // Sort columns and apply final transforms\n | project\n AvailabilityZone,\n BilledCost,\n BillingAccountId = tolower(BillingAccountId),\n BillingAccountName,\n BillingAccountType,\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEnd),\n BillingPeriodStart = startofmonth(BillingPeriodStart),\n ChargeCategory = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Credit', 'Credit',\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\n ChargeCategory\n ),\n ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass),\n ChargeDescription,\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\n ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency),\n ChargePeriodEnd,\n ChargePeriodStart,\n CommitmentDiscountCategory,\n CommitmentDiscountId = tolower(CommitmentDiscountId),\n CommitmentDiscountName,\n CommitmentDiscountStatus = case(\n // Handle FOCUS 1.0-preview ChargeSubcategory\n ChargeSubcategory == 'Used Commitment', 'Used',\n ChargeSubcategory == 'Unused Commitment', 'Unused',\n CommitmentDiscountStatus\n ),\n CommitmentDiscountType,\n ConsumedQuantity,\n ConsumedUnit,\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\n EffectiveCost,\n InvoiceIssuerName,\n ListCost,\n ListUnitPrice,\n PricingCategory = case(\n // Handle FOCUS 1.0-preview PricingCategory values\n PricingCategory == 'On-Demand', 'Standard',\n PricingCategory == 'Commitment-Based', 'Committed',\n PricingCategory\n ),\n PricingQuantity,\n PricingUnit,\n ProviderName,\n // Handle missing PublisherName values\n PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, ''),\n // Handle FOCUS 1.0-preview Region column\n RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region)),\n RegionName = coalesce(RegionName, Region),\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SkuId,\n SkuPriceId,\n SubAccountId,\n SubAccountName,\n SubAccountType, // Azure 1.0-preview(v1)+\n Tags = parse_json(Tags),\n x_AccountId, // Azure 1.0-preview(v1)+\n x_AccountName, // Azure 1.0-preview(v1)+\n x_AccountOwnerId, // Azure 1.0-preview(v1)+\n x_BilledCostInUsd, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement = case(\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\n ProviderName\n ), // Hubs add-on\n x_BillingAccountId, // Azure 1.0-preview(v1)+\n x_BillingAccountName, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate, // Azure 1.0-preview(v1)+\n x_BillingProfileId, // Azure 1.0-preview(v1)+\n x_BillingProfileName, // Azure 1.0-preview(v1)+\n x_ChargeId, // Azure 1.0-preview(v1) only\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd), // Azure 1.0+\n x_CostAllocationRuleName, // Azure 1.0-preview(v1)+\n x_CostCategories = parse_json(x_CostCategories), // AWS 1.0 (JSON)\n x_CostCenter, // Azure 1.0-preview(v1)+\n x_Credits = parse_json(x_Credits), // GCP Jan 2024\n x_CostType, // GCP Jan 2024\n x_CurrencyConversionRate, // GCP Jun 2024\n x_CustomerId, // Azure 1.0-preview(v1)+\n x_CustomerName, // Azure 1.0-preview(v1)+\n x_Discount = parse_json(x_Discount), // AWS 1.0 (JSON)\n x_EffectiveCostInUsd, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice, // Azure 1.0-preview(v1)+\n x_ExportTime, // GCP Jan 2024\n x_IngestionTime, // Hubs add-on\n x_InvoiceId = coalesce(InvoiceId, x_InvoiceId), // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId = case( // Azure 1.0-preview(v1)+\n x_InvoiceSectionId == '-2', '',\n x_InvoiceSectionId\n ),\n x_InvoiceSectionName = case( // Azure 1.0-preview(v1)+\n x_InvoiceSectionName == 'Unassigned', '',\n x_InvoiceSectionName\n ),\n x_ListCostInUsd, // Azure 1.0-preview(v1)+\n x_Location, // GCP Jan 2024\n x_Operation, // AWS 1.0\n x_PartnerCreditApplied, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate, // Azure 1.0-preview(v1)+\n x_PricingBlockSize, // Azure 1.0-preview(v1)+\n x_PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency), // Azure 1.0-preview(v1)+\n x_PricingSubcategory, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription, // Azure 1.0-preview(v1)+\n x_Project, // GCP Jan 2024\n x_PublisherCategory, // Azure 1.0-preview(v1)+\n x_PublisherId, // Azure 1.0-preview(v1)+\n x_ResellerId, // Azure 1.0-preview(v1)+\n x_ResellerName, // Azure 1.0-preview(v1)+\n x_ResourceGroupName = tolower(x_ResourceGroupName), // Azure 1.0-preview(v1)+\n x_ResourceType, // Azure 1.0-preview(v1)+\n x_ServiceCode, // AWS 1.0\n x_ServiceId, // GCP Jan 2024\n x_ServicePeriodEnd, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart, // Azure 1.0-preview(v1)+\n x_SkuDescription, // Azure 1.0-preview(v1)+\n x_SkuDetails = parse_json(x_SkuDetails), // Azure 1.0-preview(v1)+\n x_SkuIsCreditEligible, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory, // Azure 1.0-preview(v1)+\n x_SkuMeterId, // Azure 1.0-preview(v1)+\n x_SkuMeterName = coalesce(SkuMeter, x_SkuMeterName), // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory, // Azure 1.0-preview(v1)+\n x_SkuOfferId, // Azure 1.0-preview(v1)+\n x_SkuOrderId, // Azure 1.0-preview(v1)+\n x_SkuOrderName, // Azure 1.0-preview(v1)+\n x_SkuPartNumber, // Azure 1.0-preview(v1)+\n x_SkuRegion, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily, // Azure 1.0-preview(v1)+\n x_SkuTerm, // Azure 1.0-preview(v1)+\n x_SkuTier, // Azure 1.0-preview(v1)+\n x_SourceChanges, // Hubs add-on\n x_SourceName, // Hubs add-on\n x_SourceProvider, // Hubs add-on\n x_SourceType, // Hubs add-on\n x_SourceVersion, // Hubs add-on\n x_UsageType // AWS 1.0\n}\n\n// Costs_final_v1_0 table\n.create-merge table Costs_final_v1_0 (\n AvailabilityZone: string,\n BilledCost: decimal,\n BillingAccountId: string,\n BillingAccountName: string,\n BillingAccountType: string, // Azure 1.0-preview(v1)+\n BillingCurrency: string,\n BillingPeriodEnd: datetime,\n BillingPeriodStart: datetime,\n ChargeCategory: string,\n ChargeClass: string,\n ChargeDescription: string,\n ChargeFrequency: string,\n ChargePeriodEnd: datetime,\n ChargePeriodStart: datetime,\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview only\n CommitmentDiscountId: string,\n CommitmentDiscountName: string,\n CommitmentDiscountStatus: string,\n CommitmentDiscountType: string,\n ConsumedQuantity: decimal,\n ConsumedUnit: string,\n ContractedCost: decimal,\n ContractedUnitPrice: decimal,\n EffectiveCost: decimal,\n InvoiceIssuerName: string,\n ListCost: decimal,\n ListUnitPrice: decimal,\n PricingCategory: string,\n PricingQuantity: decimal,\n PricingUnit: string,\n ProviderName: string,\n PublisherName: string,\n RegionId: string,\n RegionName: string,\n ResourceId: string,\n ResourceName: string,\n ResourceType: string,\n ServiceCategory: string,\n ServiceName: string,\n SkuId: string,\n SkuPriceId: string,\n SubAccountId: string,\n SubAccountName: string,\n SubAccountType: string,\n Tags: dynamic,\n x_AccountId: string, // Azure 1.0-preview(v1)+\n x_AccountName: string, // Azure 1.0-preview(v1)+\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\n x_BilledCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_BilledUnitPrice: decimal, // Azure 1.0-preview(v1)+\n x_BillingAccountAgreement: string, // Hubs add-on\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\n x_BillingExchangeRate: decimal, // Azure 1.0-preview(v1)+\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\n x_ChargeId: string, // Azure 1.0-preview(v1) only\n x_ContractedCostInUsd: decimal, // Azure 1.0+\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\n x_CostCenter: string, // Azure 1.0-preview(v1)+\n x_Credits: dynamic, // GCP Jan 2024\n x_CostType: string, // GCP Jan 2024\n x_CurrencyConversionRate: decimal, // GCP Jun 2024\n x_CustomerId: string, // Azure 1.0-preview(v1)+\n x_CustomerName: string, // Azure 1.0-preview(v1)+\n x_Discount: dynamic, // AWS 1.0 (JSON)\n x_EffectiveCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_EffectiveUnitPrice: decimal, // Azure 1.0-preview(v1)+\n x_ExportTime: datetime, // GCP Jan 2024\n x_IngestionTime: datetime, // Hubs add-on\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\n x_ListCostInUsd: decimal, // Azure 1.0-preview(v1)+\n x_Location: string, // GCP Jan 2024\n x_Operation: string, // AWS 1.0\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\n x_PricingBlockSize: decimal, // Azure 1.0-preview(v1)+\n x_PricingCurrency: string, // Azure 1.0-preview(v1)+\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\n x_Project: string, // GCP Jan 2024\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\n x_PublisherId: string, // Azure 1.0-preview(v1)+\n x_ResellerId: string, // Azure 1.0-preview(v1)+\n x_ResellerName: string, // Azure 1.0-preview(v1)+\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\n x_ResourceType: string, // Azure 1.0-preview(v1)+\n x_ServiceCode: string, // AWS 1.0\n x_ServiceId: string, // GCP Jan 2024\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\n x_SkuMeterName: string, // Azure 1.0-preview(v1)+\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\n x_SkuTier: string, // Azure 1.0-preview(v1)+\n x_SourceChanges: string, // Hubs add-on\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_UsageType: string // AWS 1.0\n)\n\n// Update policy for Costs_raw -> Costs_final_v1_0 table\n.alter table Costs_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Costs_raw\",\n \"Query\": \"Costs_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Actual costs |===================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// ActualCosts_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use ActualCosts_transform_v1_2() instead.', folder='Costs')\nActualCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n // TODO: Transform actual costs to FOCUS 1.0 format\n ActualCosts_raw\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodStart = Date,\n ChargePeriodEnd = Date + 1d,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = '',\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory = '',\n SkuId = '',\n SkuMeter = '',\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = SubscriptionName,\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentName = '',\n x_ComponentType = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel = '',\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for ActualCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": false,\n \"Source\": \"ActualCosts_raw\",\n \"Query\": \"ActualCosts_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Amortized costs |================================================================================================\n// Supported versions:\n// - C360-2025-04\n//======================================================================================================================\n\n// AmortizedCosts_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use AmortizedCosts_transform_v1_2() instead.', folder='Costs')\nAmortizedCosts_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n // TODO: Transform actual costs to FOCUS 1.0 format\n AmortizedCosts_raw\n //\n //\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\n //\n //\n | extend x_AmortizationClass = case(\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\n ''\n )\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\n // TODO: PricingCategory needs to include savings plan usage and spot usage\n | extend PricingCategory = case(\n x_AmortizationClass == 'Amortized Charge', 'Committed',\n ChargeType in ('Usage', 'Purchase'), 'Standard',\n ''\n )\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\n | project-rename\n PricingQuantity = Quantity,\n x_PricingUnitDescription = UnitOfMeasure\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\n | join kind=leftouter (Regions) on ResourceLocation\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\n | project\n AvailabilityZone = AvailabilityZone,\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\n BillingCurrency,\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\n CapacityReservationId = '',\n CapacityReservationStatus = '',\n ChargeCategory = case(\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\n ChargeType == 'Refund', 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\n ChargeDescription = Product,\n ChargeFrequency = case(\n Frequency == 'UsageBased', 'Usage-Based',\n Frequency == 'OneTime', 'One-Time',\n Frequency // \"Recurring\" and any fallback\n ),\n ChargePeriodStart = Date,\n ChargePeriodEnd = Date + 1d,\n ChargeSubcategory = '',\n // TODO: CommitmentDiscount* columns need to handle savings plans\n CommitmentDiscountCategory,\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\n CommitmentDiscountName = ReservationName,\n CommitmentDiscountQuantity = real(null),\n CommitmentDiscountStatus = case(\n isempty(ReservationId), '',\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\n ChargeType startswith 'Unused', 'Unused',\n 'Unused'\n ),\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\n CommitmentDiscountUnit = '',\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\n ConsumedUnit = PricingUnit,\n ContractedCost = UnitPrice * PricingQuantity,\n ContractedUnitPrice = UnitPrice,\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\n InvoiceId = '',\n InvoiceIssuerName = 'Microsoft',\n ListCost = real(null),\n ListUnitPrice = real(null),\n PricingCategory,\n PricingCurrency = '',\n PricingQuantity,\n PricingUnit,\n ProviderName = 'Microsoft',\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\n Region = '',\n RegionId,\n RegionName,\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\n ResourceType,\n ServiceCategory,\n ServiceName,\n ServiceSubcategory = '',\n SkuId = '',\n SkuMeter = '',\n SkuPriceDetails = '',\n SkuPriceId = '',\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\n SubAccountName = SubscriptionName,\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\n UsageAmount = real(null),\n UsageQuantity = real(null),\n UsageUnit = '',\n x_AccountId = '',\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerId,\n x_AmortizationClass = '',\n x_BilledCostInUsd = real(null),\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\n x_BillingAccountId = BillingAccountId,\n x_BillingAccountName = BillingAccountName,\n x_BillingExchangeRate = real(null),\n x_BillingExchangeRateDate = datetime(null),\n x_BillingProfileId = BillingProfileId,\n x_BillingProfileName = BillingProfileName,\n x_BillingItemCode = '',\n x_BillingItemName = '',\n x_ChargeId = '',\n x_CommodityCode = '',\n x_CommodityName = '',\n x_ComponentName = '',\n x_ComponentType = '',\n x_ContractedCostInUsd = real(null),\n x_Cost = real(null),\n x_CostAllocationRuleName = '',\n x_CostCategories = '',\n x_CostCenter = CostCenter,\n x_CostType = '',\n x_Credits = '',\n x_CurrencyConversionRate = real(null),\n x_CustomerId = '',\n x_CustomerName = '',\n x_Discount = '',\n x_EffectiveCostInUsd = real(null),\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\n x_ExportTime = datetime(null),\n x_InstanceID = '',\n x_InvoiceId = '',\n x_InvoiceIssuerId = '',\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = InvoiceSection,\n x_ListCostInUsd = real(null),\n x_Location = '',\n x_OnDemandCost = real(null),\n x_OnDemandCostInUsd = real(null),\n x_OnDemandUnitPrice = real(null),\n x_Operation = '',\n x_OwnerAccountID = '',\n x_PartnerCreditApplied = '',\n x_PartnerCreditRate = '',\n x_PricingBlockSize,\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\n x_PricingSubcategory = case(\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\n PricingCategory == 'Standard', 'Standard',\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\n PricingCategory == 'Dynamic', 'Spot',\n ''\n ),\n x_PricingUnitDescription,\n x_Project = '',\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\n x_PublisherId = '',\n x_ResellerId = '',\n x_ResellerName = '',\n x_ResourceGroupName = ResourceGroup,\n x_ResourceType,\n x_ServiceCode = '',\n x_ServiceId = '',\n x_ServiceModel = '',\n x_ServicePeriodEnd = datetime(null),\n x_ServicePeriodStart = datetime(null),\n x_SkuDescription = Product,\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\n x_SkuIsCreditEligible = IsAzureCreditEligible,\n x_SkuMeterCategory = MeterCategory,\n x_SkuMeterId = MeterId,\n x_SkuMeterName = MeterName,\n x_SkuMeterSubcategory = MeterSubCategory,\n x_SkuOfferId = OfferId,\n x_SkuOrderId = ProductOrderId,\n x_SkuOrderName = ProductOrderName,\n x_SkuPartNumber = PartNumber,\n x_SkuPlanName = '',\n x_SkuRegion = MeterRegion,\n x_SkuServiceFamily = ServiceFamily,\n x_SkuTerm = toint(Term),\n x_SkuTier = '',\n x_SourceName = 'C360',\n x_SourceProvider = 'Microsoft',\n x_SourceType = 'ActualCost',\n x_SourceVersion = 'C360-2025-04',\n x_SubproductName = '',\n x_UsageType = ''\n}\n\n// Update policy for AmortizedCosts_raw -> Costs_raw table\n.alter table Costs_raw policy update\n``` \n[{\n \"IsEnabled\": false,\n \"Source\": \"AmortizedCosts_raw\",\n \"Query\": \"AmortizedCosts_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| CommitmentDiscountUsage |========================================================================================\n// Supported versions:\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\n//======================================================================================================================\n\n// CommitmentDiscountUsage_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All commitment discount usage transformed to FOCUS 1.0. This includes reservationdeatils_raw. Use CommitmentDiscountUsage_transform_v1_2() instead.', folder='Commitment discounts')\nCommitmentDiscountUsage_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n CommitmentDiscountUsage_raw\n //\n // Change real to decimal\n | extend\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\n ReservedHours = todecimal(ReservedHours),\n TotalReservedQuantity = todecimal(TotalReservedQuantity),\n UsedHours = todecimal(UsedHours)\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Handle resource columns\n | extend ResourceId = tolower(InstanceId)\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, x_ServiceModel) on x_ResourceType\n //\n // Sort columns and apply final transforms\n | project\n ChargePeriodEnd = UsageDate + 1d,\n ChargePeriodStart = UsageDate,\n CommitmentDiscountCategory = 'Usage',\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\n CommitmentDiscountType = 'Reservation',\n ConsumedQuantity = UsedHours,\n ProviderName,\n ResourceId,\n ResourceName,\n ResourceType,\n ServiceCategory,\n ServiceName,\n SubAccountId,\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\n x_CommitmentDiscountCommittedAmount = ReservedHours,\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\n x_CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\n x_IngestionTime = ingestion_time(),\n x_ResourceGroupName,\n x_ResourceType,\n // x_RowId = hash_sha256(strcat(\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\n // CommitmentDiscountId,\n // ResourceId,\n // ChargePeriodStart\n // )),\n x_ServiceModel,\n x_SkuOrderId = ReservationOrderId,\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\n}\n\n// CommitmentDiscountUsage_final_v1_0 table\n.create-merge table CommitmentDiscountUsage_final_v1_0 (\n ChargePeriodEnd: datetime, // Hubs add-on\n ChargePeriodStart: datetime, // MS 2023-03-01\n CommitmentDiscountCategory: string, // Hubs add-on\n CommitmentDiscountId: string, // MS 2023-03-01\n CommitmentDiscountType: string, // Hubs add-on\n ConsumedQuantity: decimal, // MS 2023-03-01\n ProviderName: string, // Hubs add-on\n ResourceId: string, // MS 2023-03-01\n ResourceName: string, // Hubs add-on\n ResourceType: string, // Hubs add-on\n ServiceCategory: string, // Hubs add-on\n ServiceName: string, // Hubs add-on\n SubAccountId: string, // Hubs add-on\n x_CommitmentDiscountCommittedCount: decimal, // MS 2023-03-01\n x_CommitmentDiscountCommittedAmount: decimal, // MS 2023-03-01\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\n x_CommitmentDiscountNormalizedRatio: decimal, // MS 2023-03-01\n x_CommitmentDiscountQuantity: decimal, // MS 2023-03-01\n x_IngestionTime: datetime, // Hubs add-on\n x_ResourceGroupName: string, // Hubs add-on\n x_ResourceType: string, // Hubs add-on\n x_ServiceModel: string, // Hubs add-on\n x_SkuOrderId: string, // MS 2023-03-01\n x_SkuSize: string, // MS 2023-03-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string // Hubs add-on\n)\n\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_0 table\n.alter table CommitmentDiscountUsage_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"CommitmentDiscountUsage_raw\",\n \"Query\": \"CommitmentDiscountUsage_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Recommendations |================================================================================================\n// Supported datasets/versions:\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\n//======================================================================================================================\n\n// Recommendations_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All recommendations transformed to FOCUS 1.0. Use Recommendations_transform_v1_2() instead.', folder='Recommendations')\nRecommendations_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Recommendations_raw\n //\n // Change real to decimal\n | extend\n CostWithNoReservedInstances = todecimal(CostWithNoReservedInstances),\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\n NetSavings = todecimal(NetSavings),\n RecommendedQuantity = todecimal(RecommendedQuantity),\n RecommendedQuantityNormalized = todecimal(RecommendedQuantityNormalized),\n TotalCostWithReservedInstances = todecimal(TotalCostWithReservedInstances)\n //\n | extend x_IngestionTime = ingestion_time()\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Convert JSON cost columns to decimal\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\n //\n // Build recommendation details\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\n | extend x_RecommendationDetails = case(\n x_SourceType == 'ReservationRecommendations', bag_pack(\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\n 'CommitmentDiscountResourceType', ResourceType,\n 'CommitmentDiscountScope', Scope,\n 'LookbackPeriodDuration', case(\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\n ''\n ),\n 'LookbackPeriodStart', FirstUsageDate,\n 'RecommendedQuantity', RecommendedQuantity,\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\n 'RegionId', Location,\n 'RegionName', RegionName,\n 'SkuMeterId', MeterId,\n 'SkuPriceDetails', SkuProperties,\n 'SkuSize', coalesce(SKU, SkuName),\n 'SkuTerm', isoMonths(Term)\n ),\n dynamic({})\n )\n //\n // Sort columns and apply final transforms\n | extend x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d)\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\n | project\n ProviderName,\n SubAccountId = iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), ''),\n x_IngestionTime,\n x_EffectiveCostAfter = TotalCostWithReservedInstances,\n x_EffectiveCostBefore = CostWithNoReservedInstances,\n x_EffectiveCostSavings = NetSavings,\n x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d),\n x_RecommendationDetails,\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion\n}\n\n// Recommendations_final_v1_0 table\n.create-merge table Recommendations_final_v1_0 (\n ProviderName: string,\n SubAccountId: string,\n x_IngestionTime: datetime,\n x_EffectiveCostAfter: decimal,\n x_EffectiveCostBefore: decimal,\n x_EffectiveCostSavings: decimal,\n x_RecommendationDate: datetime,\n x_RecommendationDetails: dynamic,\n x_SourceName: string,\n x_SourceProvider: string,\n x_SourceType: string,\n x_SourceVersion: string\n)\n\n// Update policy for Recommendations_raw -> Recommendations_final_v1_0 table\n.alter table Recommendations_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Recommendations_raw\",\n \"Query\": \"Recommendations_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n\n\n//===| Transactions |===================================================================================================\n// Supported versions:\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\n//======================================================================================================================\n\n// Transactions_transform_v1_0 function\n.create-or-alter function\nwith (docstring='DEPRECATED: All transactions transformed to FOCUS 1.0. Use Transactions_transform_v1_2() instead.', folder='Transactions')\nTransactions_transform_v1_0()\n{\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\n let isoMonths = (duration: string) {\n let number = toint(replace_regex(duration, @'[PMY]', ''));\n toint(case(\n duration == '', toint(''),\n duration endswith \"Y\", number * 12,\n duration endswith \"M\", number,\n -1\n ))\n };\n Transactions_raw\n //\n // Change real to decimal\n | extend\n Amount = todecimal(Amount),\n MonetaryCommitment = todecimal(MonetaryCommitment),\n Overage = todecimal(Overage),\n Quantity = todecimal(Quantity)\n //\n // Set ProviderName\n | extend ProviderName = 'Microsoft'\n //\n // Set source columns\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\n //\n // Handle BillingPeriodStart/End\n | extend BillingMonth = tostring(BillingMonth)\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\n //\n // Sort columns and apply final transforms\n | project\n BilledCost = Amount,\n BillingAccountId = case(\n BillingProfileId startswith '/', BillingProfileId,\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\n ''\n ),\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\n BillingCurrency = Currency,\n BillingPeriodEnd,\n BillingPeriodStart,\n ChargeCategory = case(\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\n 'Adjustment'\n ),\n ChargeClass = case(\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\n EventType == 'Refund', 'Correction',\n ''\n ),\n ChargeDescription = Description,\n ChargeFrequency = case(\n BillingFrequency == 'OneTime', 'One-Time',\n BillingFrequency == 'Recurring', 'Recurring',\n BillingFrequency\n ),\n ChargePeriodStart = EventDate,\n PricingQuantity = Quantity,\n PricingUnit = 'Reservations',\n ProviderName,\n RegionId = Region,\n RegionName = Region,\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\n x_AccountName = AccountName,\n x_AccountOwnerId = AccountOwnerEmail,\n x_CostCenter = CostCenter,\n x_InvoiceId = InvoiceId,\n x_InvoiceNumber = Invoice,\n x_InvoiceSectionId = InvoiceSectionId,\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\n x_IngestionTime = ingestion_time(),\n x_MonetaryCommitment = MonetaryCommitment,\n x_Overage = Overage,\n x_PurchasingBillingAccountId = PurchasingEnrollment,\n x_SkuOrderId = ReservationOrderId,\n x_SkuOrderName = ReservationOrderName,\n x_SkuSize = ArmSkuName,\n x_SkuTerm = isoMonths(Term),\n x_SourceName,\n x_SourceProvider,\n x_SourceType,\n x_SourceVersion,\n x_SubscriptionId = PurchasingSubscriptionGuid,\n x_TransactionType = EventType\n}\n\n// Transactions_final_v1_0 table\n.create-merge table Transactions_final_v1_0 (\n BilledCost: decimal, // MS CM EA+MCA 2023-05-01\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n ChargeCategory: string, // Hubs add-on\n ChargeClass: string, // Hubs add-on\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\n PricingQuantity: decimal, // MS CM EA+MCA 2023-05-01\n PricingUnit: string, // Hubs add-on\n ProviderName: string, // Hubs add-on\n RegionId: string, // MS CM EA+MCA 2023-05-01\n RegionName: string, // MS CM EA+MCA 2023-05-01\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\n x_AccountName: string, // MS CM EA 2023-05-01\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\n x_CostCenter: string, // MS CM EA 2023-05-01\n x_InvoiceId: string, // MS CM MCA 2023-05-01\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\n x_IngestionTime: datetime, // Hubs add-on\n x_MonetaryCommitment: decimal, // MS CM EA 2023-05-01\n x_Overage: decimal, // MS CM EA 2023-05-01\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\n x_SourceName: string, // Hubs add-on\n x_SourceProvider: string, // Hubs add-on\n x_SourceType: string, // Hubs add-on\n x_SourceVersion: string, // Hubs add-on\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\n)\n\n// Update policy for Transactions_raw -> Transactions_final_v1_0 table\n.alter table Transactions_final_v1_0 policy update\n```\n[{\n \"IsEnabled\": false,\n \"Source\": \"Transactions_raw\",\n \"Query\": \"Transactions_transform_v1_0()\",\n \"IsTransactional\": true,\n \"PropagateIngestionProperties\": true\n}]\n```\n", - "CONFIG": "config", - "HUB_DATA_EXPLORER": "hubDataExplorer", - "HUB_DB": "Hub", - "INGESTION": "ingestion", - "INGESTION_DB": "Ingestion", - "INGESTION_ID_SEPARATOR": "__", - "ftkReleaseUri": "[if(endsWith(variables('finOpsToolkitVersion'), '-dev'), 'https://github.com/microsoft/finops-toolkit/releases/latest/download', format('https://github.com/microsoft/finops-toolkit/releases/download/v{0}', variables('finOpsToolkitVersion')))]", - "useFabric": "[not(empty(parameters('fabricQueryUri')))]", - "useAzure": "[and(not(variables('useFabric')), not(empty(parameters('clusterName'))))]", - "dataExplorerPrivateDnsZoneName": "[replace(format('privatelink.{0}.{1}', parameters('app').hub.location, replace(environment().suffixes.storage, 'core', 'kusto')), '..', '.')]", - "ingestionCapacity": { - "Dev(No SLA)_Standard_E2a_v4": 1, - "Dev(No SLA)_Standard_D11_v2": 1, - "Standard_D11_v2": 2, - "Standard_D12_v2": 4, - "Standard_D13_v2": 8, - "Standard_D14_v2": 16, - "Standard_D16d_v5": 16, - "Standard_D32d_v4": 32, - "Standard_D32d_v5": 32, - "Standard_DS13_v2+1TB_PS": 8, - "Standard_DS13_v2+2TB_PS": 8, - "Standard_DS14_v2+3TB_PS": 16, - "Standard_DS14_v2+4TB_PS": 16, - "Standard_E2a_v4": 2, - "Standard_E2ads_v5": 2, - "Standard_E2d_v4": 2, - "Standard_E2d_v5": 2, - "Standard_E4a_v4": 4, - "Standard_E4ads_v5": 4, - "Standard_E4d_v4": 4, - "Standard_E4d_v5": 4, - "Standard_E8a_v4": 8, - "Standard_E8ads_v5": 8, - "Standard_E8as_v4+1TB_PS": 8, - "Standard_E8as_v4+2TB_PS": 8, - "Standard_E8as_v5+1TB_PS": 8, - "Standard_E8as_v5+2TB_PS": 8, - "Standard_E8d_v4": 8, - "Standard_E8d_v5": 8, - "Standard_E8s_v4+1TB_PS": 8, - "Standard_E8s_v4+2TB_PS": 8, - "Standard_E8s_v5+1TB_PS": 8, - "Standard_E8s_v5+2TB_PS": 8, - "Standard_E16a_v4": 16, - "Standard_E16ads_v5": 16, - "Standard_E16as_v4+3TB_PS": 16, - "Standard_E16as_v4+4TB_PS": 16, - "Standard_E16as_v5+3TB_PS": 16, - "Standard_E16as_v5+4TB_PS": 16, - "Standard_E16d_v4": 16, - "Standard_E16d_v5": 16, - "Standard_E16s_v4+3TB_PS": 16, - "Standard_E16s_v4+4TB_PS": 16, - "Standard_E16s_v5+3TB_PS": 16, - "Standard_E16s_v5+4TB_PS": 16, - "Standard_E64i_v3": 64, - "Standard_E80ids_v4": 80, - "Standard_EC8ads_v5": 8, - "Standard_EC8as_v5+1TB_PS": 8, - "Standard_EC8as_v5+2TB_PS": 8, - "Standard_EC16ads_v5": 16, - "Standard_EC16as_v5+3TB_PS": 16, - "Standard_EC16as_v5+4TB_PS": 16, - "Standard_L4s": 4, - "Standard_L8as_v3": 8, - "Standard_L8s": 8, - "Standard_L8s_v2": 8, - "Standard_L8s_v3": 8, - "Standard_L16as_v3": 16, - "Standard_L16s": 16, - "Standard_L16s_v2": 16, - "Standard_L16s_v3": 16, - "Standard_L32as_v3": 32, - "Standard_L32s_v3": 32 - }, - "dataExplorerIngestionCapacity": "[if(variables('useFabric'), parameters('fabricCapacityUnits'), if(not(variables('useAzure')), 1, coalesce(tryGet(variables('ingestionCapacity'), parameters('clusterSku')), 1)))]", - "dataExplorerUri": "[if(variables('useFabric'), parameters('fabricQueryUri'), format('https://{0}.{1}.kusto.windows.net', replace(parameters('clusterName'), '_', '-'), parameters('app').hub.location))]", - "finOpsToolkitVersion": "12.0" - }, - "resources": { - "cluster::adfClusterAdmin": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Kusto/clusters/principalAssignments", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), 'adf-mi-cluster-admin')]", - "properties": { - "principalType": "App", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[reference('dataFactory', '2018-06-01', 'full').identity.tenantId]", - "role": "AllDatabasesAdmin" - }, - "dependsOn": [ - "cluster", - "dataFactory" - ] - }, - "cluster::ingestionDb": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Kusto/clusters/databases", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), variables('INGESTION_DB'))]", - "location": "[parameters('app').hub.location]", - "kind": "ReadWrite", - "dependsOn": [ - "cluster" - ] - }, - "cluster::hubDb": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Kusto/clusters/databases", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', replace(parameters('clusterName'), '_', '-'), variables('HUB_DB'))]", - "location": "[parameters('app').hub.location]", - "kind": "ReadWrite", - "dependsOn": [ - "cluster" - ] - }, - "dataFactoryVNet::dataExplorerManagedPrivateEndpoint": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', variables('HUB_DATA_EXPLORER'))]", - "properties": { - "name": "[variables('HUB_DATA_EXPLORER')]", - "groupId": "cluster", - "privateLinkResourceId": "[resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-'))]", - "fqdns": [ - "[format('https://{0}.{1}.kusto.windows.net', replace(parameters('clusterName'), '_', '-'), parameters('app').hub.location)]" - ] - }, - "dependsOn": [ - "appRegistration", - "cluster" - ] - }, - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "dependsOn": [ - "appRegistration" - ] - }, - "blobPrivateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "dependsOn": [ - "appRegistration" - ] - }, - "queuePrivateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.queue.{0}', environment().suffixes.storage)]", - "dependsOn": [ - "appRegistration" - ] - }, - "tablePrivateDnsZone": { - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.table.{0}', environment().suffixes.storage)]", - "dependsOn": [ - "appRegistration" - ] - }, - "storage": { - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "dependsOn": [ - "appRegistration" - ] - }, - "cluster": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Kusto/clusters", - "apiVersion": "2023-08-15", - "name": "[replace(parameters('clusterName'), '_', '-')]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Kusto/clusters'), createObject()))]", - "sku": { - "name": "[parameters('clusterSku')]", - "tier": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 'Basic', 'Standard')]", - "capacity": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 1, if(equals(parameters('clusterCapacity'), 1), 2, parameters('clusterCapacity')))]" - }, - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "enableStreamingIngest": true, - "enableAutoStop": false, - "publicNetworkAccess": "[if(parameters('app').hub.options.privateRouting, 'Disabled', 'Enabled')]" - }, - "dependsOn": [ - "appRegistration" - ] - }, - "clusterStorageAccess": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(replace(parameters('clusterName'), '_', '-'), subscription().id, 'Storage Blob Data Contributor')]", - "properties": { - "description": "Give \"Storage Blob Data Contributor\" to the cluster", - "principalId": "[reference('cluster', '2023-08-15', 'full').identity.principalId]", - "principalType": "ServicePrincipal", - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" - }, - "dependsOn": [ - "appRegistration", - "cluster" - ] - }, - "dataExplorerPrivateDnsZone": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[variables('dataExplorerPrivateDnsZoneName')]", - "location": "global", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateDnsZones'), createObject()))]", - "properties": {} - }, - "dataExplorerPrivateDnsZoneLink": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', variables('dataExplorerPrivateDnsZoneName'), format('{0}-link', replace(variables('dataExplorerPrivateDnsZoneName'), '.', '-')))]", - "location": "global", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateDnsZones/virtualNetworkLinks'), createObject()))]", - "properties": { - "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "dataExplorerPrivateDnsZone" - ] - }, - "dataExplorerEndpoint": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', replace(parameters('clusterName'), '_', '-'))]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Network/privateEndpoints'), createObject()))]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.dataExplorer]" - }, - "privateLinkServiceConnections": [ - { - "name": "dataExplorerLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-'))]", - "groupIds": [ - "cluster" - ] - } - } - ] - }, - "dependsOn": [ - "cluster" - ] - }, - "dataExplorerPrivateDnsZoneGroup": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', replace(parameters('clusterName'), '_', '-')), 'dataExplorer-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "privatelink-westus-kusto-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" - } - }, - { - "name": "privatelink-blob-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - }, - { - "name": "privatelink-table-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.table.{0}', environment().suffixes.storage))]" - } - }, - { - "name": "privatelink-queue-core-windows-net", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "appRegistration", - "dataExplorerEndpoint", - "dataExplorerPrivateDnsZone" - ] - }, - "dataFactoryVNet": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "dependsOn": [ - "appRegistration" - ] - }, - "linkedService_dataExplorer": { - "condition": "[or(variables('useAzure'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('HUB_DATA_EXPLORER'))]", - "properties": "[shallowMerge(createArray(createObject('type', 'AzureDataExplorer', 'parameters', createObject('database', createObject('type', 'String', 'defaultValue', variables('INGESTION_DB'))), 'typeProperties', createObject('endpoint', variables('dataExplorerUri'), 'database', '@{linkedService().database}', 'tenant', reference('dataFactory', '2018-06-01', 'full').identity.tenantId, 'servicePrincipalId', reference('dataFactory', '2018-06-01', 'full').identity.principalId)), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]", - "dependsOn": [ - "appRegistration", - "cluster", - "dataFactory" - ] - }, - "linkedService_ftkRepo": { - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ftkRepo')]", - "properties": "[shallowMerge(createArray(createObject('type', 'HttpServer', 'parameters', createObject('filePath', createObject('type', 'string')), 'typeProperties', createObject('url', '@concat(''https://gitapp.hub.com/microsoft/finops-toolkit/'', linkedService().filePath)', 'enableServerCertificateValidation', true(), 'authenticationType', 'Anonymous')), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]", - "dependsOn": [ - "appRegistration" - ] - }, - "dataset_dataExplorer": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, variables('HUB_DATA_EXPLORER'))]", - "properties": { - "type": "AzureDataExplorerTable", - "linkedServiceName": { - "parameters": { - "database": "@dataset().database" - }, - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference" - }, - "parameters": { - "database": { - "type": "String", - "defaultValue": "[variables('INGESTION_DB')]" - }, - "table": { - "type": "String" - } - }, - "typeProperties": { - "table": { - "value": "@dataset().table", - "type": "Expression" - } - } - }, - "dependsOn": [ - "appRegistration", - "linkedService_dataExplorer" - ] - }, - "dataset_ftkReleaseFile": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ftkReleaseFile')]", - "properties": { - "linkedServiceName": { - "referenceName": "ftkRepo", - "type": "LinkedServiceReference" - }, - "parameters": { - "fileName": { - "type": "string" - }, - "version": { - "type": "string", - "defaultValue": "[variables('finOpsToolkitVersion')]" - } - }, - "annotations": [], - "type": "DelimitedText", - "typeProperties": { - "location": { - "type": "HttpServerLocation", - "relativeUrl": { - "value": "@concat('releases/download/v', dataset().version, '/', dataset().fileName)", - "type": "Expression" - } - }, - "columnDelimiter": ",", - "escapeChar": "\\", - "firstRowAsHeader": true, - "quoteChar": "\"" - }, - "schema": [] - }, - "dependsOn": [ - "appRegistration", - "linkedService_ftkRepo" - ] - }, - "pipeline_InitializeHub": { - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_InitializeHub', variables('CONFIG')))]", - "properties": { - "activities": [ - { - "name": "Get Config", - "type": "Lookup", - "dependsOn": [], - "policy": { - "timeout": "0.00:05:00", - "retry": 2, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": true, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference" - } - } - }, - { - "name": "Set Version", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "version", - "value": { - "value": "@activity('Get Config').output.firstRow.version", - "type": "Expression" - } - } - }, - { - "name": "Set Scopes", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "scopes", - "value": { - "value": "@string(activity('Get Config').output.firstRow.scopes)", - "type": "Expression" - } - } - }, - { - "name": "Set Retention", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Get Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "variableName": "retention", - "value": { - "value": "@string(activity('Get Config').output.firstRow.retention)", - "type": "Expression" - } - } - }, - { - "name": "Until Capacity Is Available", - "type": "Until", - "dependsOn": [ - { - "activity": "Set Version", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Scopes", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Retention", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@equals(variables('tryAgain'), false)", - "type": "Expression" - }, - "activities": [ - { - "name": "Confirm Ingestion Capacity", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "If Has Capacity", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Confirm Ingestion Capacity", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", - "type": "Expression" - }, - "ifFalseActivities": [ - { - "name": "Wait for Ingestion", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 15 - } - }, - { - "name": "Try Again", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait for Ingestion", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": true - } - } - ], - "ifTrueActivities": [ - { - "name": "Set ingestion policy in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": { - "value": "[if(variables('useFabric'), format('.show database {0} policy managed_identity', variables('INGESTION_DB')), format('.alter-merge database {0} policy managed_identity \"[ {{ ''ObjectId'' : ''{1}'', ''AllowedUsages'' : ''NativeIngestion'' }}]\"', variables('INGESTION_DB'), reference('cluster', '2023-08-15', 'full').identity.principalId))]", - "type": "Expression" - }, - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Save Hub Settings in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Set ingestion policy in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": { - "value": "@concat('.append HubSettingsLog <| print version=\"', variables('version'), '\",scopes=dynamic(', variables('scopes'), '),retention=dynamic(', variables('retention'), ') | extend scopes = iff(isnull(scopes[0]), pack_array(scopes), scopes) | mv-apply scopeObj = scopes on (where isnotempty(scopeObj.scope) | summarize scopes = make_set(scopeObj.scope))')", - "type": "Expression" - }, - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Update PricingUnits in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Save Hub Settings in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace PricingUnits <| externaldata(x_PricingUnitDescription: string, AccountTypes: string, x_PricingBlockSize: decimal, PricingUnit: string)[@\"{0}/PricingUnits.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away AccountTypes', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Update Regions in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update PricingUnits in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace Regions <| externaldata(ResourceLocation: string, RegionId: string, RegionName: string)[@\"{0}/Regions.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Update ResourceTypes in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update Regions in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace ResourceTypes <| externaldata(x_ResourceType: string, SingularDisplayName: string, PluralDisplayName: string, LowerSingularDisplayName: string, LowerPluralDisplayName: string, IsPreview: bool, Description: string, IconUri: string, Links: string)[@\"{0}/ResourceTypes.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away Links', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Update Services in ADX", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Update ResourceTypes in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": "[format('.set-or-replace Services <| externaldata(x_ConsumedService: string, x_ResourceType: string, ServiceName: string, ServiceCategory: string, ServiceSubcategory: string, PublisherName: string, x_PublisherCategory: string, x_Environment: string, x_ServiceModel: string)[@\"{0}/Services.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Ingestion Complete", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Update Services in ADX", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - } - ] - } - }, - { - "name": "Abort On Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "If Has Capacity", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - } - ], - "timeout": "0.02:00:00" - } - }, - { - "name": "Timeout Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Until Capacity Is Available", - "dependencyConditions": [ - "Failed" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": "Data Explorer ingestion timed out after 2 hours while waiting for available capacity. Please re-run this pipeline to re-attempt ingestion. If you continue to see this error, please report an issue at https://aka.ms/ftk/ideas.", - "errorCode": "DataExplorerIngestionTimeout" - } - } - ], - "concurrency": 1, - "variables": { - "version": { - "type": "String" - }, - "scopes": { - "type": "String" - }, - "retention": { - "type": "String" - }, - "tryAgain": { - "type": "Boolean", - "defaultValue": true - } - } - }, - "dependsOn": [ - "appRegistration", - "cluster", - "linkedService_dataExplorer" - ], - "metadata": { - "description": "Initializes the hub instance based on the configuration settings." - } - }, - "pipeline_ToDataExplorer": { - "condition": "[or(variables('useAzure'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ETL_dataExplorer', variables('INGESTION')))]", - "properties": { - "activities": [ - { - "name": "Read Hub Config", - "description": "Read the hub config to determine how long data should be retained.", - "type": "Lookup", - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "source": { - "type": "JsonSource", - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "recursive": false, - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "JsonReadSettings" - } - }, - "dataset": { - "referenceName": "[variables('CONFIG')]", - "type": "DatasetReference", - "parameters": { - "fileName": "settings.json", - "folderPath": "[variables('CONFIG')]" - } - } - } - }, - { - "name": "Set Final Retention Months", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Read Hub Config", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "finalRetentionMonths", - "value": { - "value": "@coalesce(activity('Read Hub Config').output.firstRow.retention.final.months, 999)", - "type": "Expression" - } - } - }, - { - "name": "Until Capacity Is Available", - "type": "Until", - "dependsOn": [ - { - "activity": "Set Final Retention Months", - "dependencyConditions": [ - "Completed", - "Skipped" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@equals(variables('tryAgain'), false)", - "type": "Expression" - }, - "activities": [ - { - "name": "Confirm Ingestion Capacity", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference" - } - }, - { - "name": "If Has Capacity", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Confirm Ingestion Capacity", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", - "type": "Expression" - }, - "ifFalseActivities": [ - { - "name": "Wait for Ingestion", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 15 - } - }, - { - "name": "Try Again", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait for Ingestion", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": true - } - } - ], - "ifTrueActivities": [ - { - "name": "Pre-Ingest Cleanup", - "description": "Cost Management exports include all month-to-date data from the previous export run. To ensure data is not double-reported, it must be dropped from the raw table before ingestion completes. Remove previous ingestions into the raw table for the month and any previous runs of the current ingestion month file in any table.", - "type": "AzureDataExplorerCommand", - "dependsOn": [], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "typeProperties": { - "command": { - "value": "@concat('.drop extents <| .show extents | where (TableName == \"', pipeline().parameters.table, '\" and Tags !has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '\") or (Tags has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '/', pipeline().parameters.originalFileName, '\")')", - "type": "Expression" - }, - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Ingest Data", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Pre-Ingest Cleanup", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 3, - "retryIntervalInSeconds": 120, - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "command": { - "value": "[format('@concat(''.ingest into table '', pipeline().parameters.table, '' (\"abfss://{0}@{1}.dfs.{2}/'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.fileName, '';{3}\") with (format=\"parquet\", ingestionMappingReference=\"'', pipeline().parameters.table, ''_mapping\", tags=\"[\\\"drop-by:'', pipeline().parameters.ingestionId, ''\\\", \\\"drop-by:'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.originalFileName, ''\\\", \\\"drop-by:ftk-version-{4}\\\"]\"); print Success = assert(iff(toscalar($command_results | project-keep HasErrors) == false, true, false), \"Ingestion Failed\")'')', variables('INGESTION'), parameters('app').storage, environment().suffixes.storage, if(variables('useFabric'), 'impersonate', 'managed_identity=system'), variables('finOpsToolkitVersion'))]", - "type": "Expression" - }, - "commandTimeout": "01:00:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Post-Ingest Cleanup", - "description": "Cost Management exports include all month-to-date data from the previous export run. To ensure data is not double-reported, it must be dropped after ingestion completes. Remove the current ingestion month file from raw and any old ingestions for the month from the final table.", - "type": "AzureDataExplorerCommand", - "dependsOn": [ - { - "activity": "Ingest Data", - "dependencyConditions": [ - "Completed" - ] - } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false - }, - "typeProperties": { - "command": { - "value": "@concat('.drop extents <| .show extents | extend isOldFinalData = (TableName startswith \"', replace(pipeline().parameters.table, '_raw', '_final_v'), '\" and Tags !has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '\") | extend isPastFinalRetention = (TableName startswith \"', replace(pipeline().parameters.table, '_raw', '_final_v'), '\" and todatetime(substring(strcat(replace_string(extract(\"drop-by:[A-Za-z]+/(\\\\d{4}/\\\\d{2}(/\\\\d{2})?)\", 1, Tags), \"/\", \"-\"), \"-01\"), 0, 10)) < datetime_add(\"month\", -', if(lessOrEquals(variables('finalRetentionMonths'), 0), 0, variables('finalRetentionMonths')), ', startofmonth(now()))) | where isOldFinalData or isPastFinalRetention')", - "type": "Expression" - }, - "commandTimeout": "00:20:00" - }, - "linkedServiceName": { - "referenceName": "[variables('HUB_DATA_EXPLORER')]", - "type": "LinkedServiceReference", - "parameters": { - "database": "[variables('INGESTION_DB')]" - } - } - }, - { - "name": "Ingestion Complete", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Post-Ingest Cleanup", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - }, - { - "name": "Abort On Ingestion Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Ingest Data", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - }, - { - "name": "Ingestion Failed Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Abort On Ingestion Error", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Data Explorer ingestion into the ', pipeline().parameters.table, ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Ingest Data').output.errors), 0), activity('Ingest Data').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Ingest Data').output.errors), 0), activity('Ingest Data').output.errors[0].Code, 'None'), ')')", - "type": "Expression" - }, - "errorCode": "DataExplorerIngestionFailed" - } - }, - { - "name": "Abort On Pre-Ingest Drop Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Pre-Ingest Cleanup", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - }, - { - "name": "Pre-Ingest Drop Failed Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Abort On Pre-Ingest Drop Error", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Data Explorer pre-ingestion cleanup (drop extents from raw table) for the ', pipeline().parameters.table, ' table failed. Ingestion was not completed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", - "type": "Expression" - }, - "errorCode": "DataExplorerPreIngestionDropFailed" - } - }, - { - "name": "Abort On Post-Ingest Drop Error", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Post-Ingest Cleanup", - "dependencyConditions": [ - "Failed" - ] - } - ], - "policy": { - "secureOutput": false, - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "variableName": "tryAgain", - "value": false - } - }, - { - "name": "Post-Ingest Drop Failed Error", - "type": "Fail", - "dependsOn": [ - { - "activity": "Abort On Post-Ingest Drop Error", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Data Explorer post-ingestion cleanup (drop extents from final tables) for the ', replace(pipeline().parameters.table, '_raw', '_final_*'), ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", - "type": "Expression" - }, - "errorCode": "DataExplorerPostIngestionDropFailed" - } - } - ] - } - } - ], - "timeout": "0.02:00:00" + "filesUploaded": { + "type": "int", + "metadata": { + "description": "The number of files uploaded to the storage container." + }, + "value": "[variables('fileCount')]" + }, + "identityId": { + "type": "string", + "metadata": { + "description": "Resource ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.id.value, '')]" + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Name of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.name.value, '')]" + }, + "identityPrincipalId": { + "type": "string", + "metadata": { + "description": "Principal ID of the user assigned identity used to upload files. Will be empty if no files are uploaded or forceCreateBlobManagerIdentity is false." + }, + "value": "[if(or(variables('hasFiles'), parameters('forceCreateBlobManagerIdentity')), reference('identity').outputs.principalId.value, '')]" + } + } + } + }, + "dependsOn": [ + "appRegistration" + ] + } + }, + "outputs": { + "exportContainer": { + "type": "string", + "metadata": { + "description": "Name of the container used for Cost Management exports." + }, + "value": "[reference('exportContainer').outputs.containerName.value]" + }, + "schemaFilesUploaded": { + "type": "int", + "metadata": { + "description": "Number of schema files uploaded." + }, + "value": "[reference('schemaFiles').outputs.filesUploaded.value]" + } + } + } + }, + "dependsOn": [ + "core" + ] + }, + "dataExplorer": { + "condition": "[variables('deployDataExplorer')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "dataExplorer", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('dataExplorerName')]" + }, + "clusterSku": { + "value": "[parameters('dataExplorerSku')]" + }, + "clusterCapacity": { + "value": "[parameters('dataExplorerCapacity')]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[variables('hub').tags]" + }, + "tagsByResource": { + "value": "[parameters('tagsByResource')]" + }, + "dataFactoryName": { + "value": "[reference('core').outputs.dataFactoryName.value]" + }, + "rawRetentionInDays": { + "value": "[parameters('dataExplorerRawRetentionInDays')]" + }, + "virtualNetworkId": "[if(parameters('enablePublicAccess'), createObject('value', ''), createObject('value', reference('infrastructure').outputs.vNetId.value))]", + "privateEndpointSubnetId": "[if(parameters('enablePublicAccess'), createObject('value', ''), createObject('value', reference('infrastructure').outputs.dataExplorerSubnetId.value))]", + "enablePublicAccess": { + "value": "[parameters('enablePublicAccess')]" + }, + "storageAccountName": { + "value": "[reference('core').outputs.storageAccountName.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "12711851392414163333" + } + }, + "parameters": { + "clusterName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics. If empty, Azure Data Explorer will not be deployed. Required to use with Power BI if you have more than $2-5M/mo in costs being monitored. Default: \"\" (do not use)." + } + }, + "clusterSku": { + "type": "string", + "defaultValue": "Dev(No SLA)_Standard_E2a_v4", + "allowedValues": [ + "Dev(No SLA)_Standard_E2a_v4", + "Dev(No SLA)_Standard_D11_v2", + "Standard_D11_v2", + "Standard_D12_v2", + "Standard_D13_v2", + "Standard_D14_v2", + "Standard_D16d_v5", + "Standard_D32d_v4", + "Standard_D32d_v5", + "Standard_DS13_v2+1TB_PS", + "Standard_DS13_v2+2TB_PS", + "Standard_DS14_v2+3TB_PS", + "Standard_DS14_v2+4TB_PS", + "Standard_E2a_v4", + "Standard_E2ads_v5", + "Standard_E2d_v4", + "Standard_E2d_v5", + "Standard_E4a_v4", + "Standard_E4ads_v5", + "Standard_E4d_v4", + "Standard_E4d_v5", + "Standard_E8a_v4", + "Standard_E8ads_v5", + "Standard_E8as_v4+1TB_PS", + "Standard_E8as_v4+2TB_PS", + "Standard_E8as_v5+1TB_PS", + "Standard_E8as_v5+2TB_PS", + "Standard_E8d_v4", + "Standard_E8d_v5", + "Standard_E8s_v4+1TB_PS", + "Standard_E8s_v4+2TB_PS", + "Standard_E8s_v5+1TB_PS", + "Standard_E8s_v5+2TB_PS", + "Standard_E16a_v4", + "Standard_E16ads_v5", + "Standard_E16as_v4+3TB_PS", + "Standard_E16as_v4+4TB_PS", + "Standard_E16as_v5+3TB_PS", + "Standard_E16as_v5+4TB_PS", + "Standard_E16d_v4", + "Standard_E16d_v5", + "Standard_E16s_v4+3TB_PS", + "Standard_E16s_v4+4TB_PS", + "Standard_E16s_v5+3TB_PS", + "Standard_E16s_v5+4TB_PS", + "Standard_E64i_v3", + "Standard_E80ids_v4", + "Standard_EC8ads_v5", + "Standard_EC8as_v5+1TB_PS", + "Standard_EC8as_v5+2TB_PS", + "Standard_EC16ads_v5", + "Standard_EC16as_v5+3TB_PS", + "Standard_EC16as_v5+4TB_PS", + "Standard_L4s", + "Standard_L8as_v3", + "Standard_L8s", + "Standard_L8s_v2", + "Standard_L8s_v3", + "Standard_L16as_v3", + "Standard_L16s", + "Standard_L16s_v2", + "Standard_L16s_v3", + "Standard_L32as_v3", + "Standard_L32s_v3" + ], + "metadata": { + "description": "Optional. Name of the Azure Data Explorer SKU. Default: \"Dev(No SLA)_Standard_E2a_v4\"." + } + }, + "clusterCapacity": { + "type": "int", + "defaultValue": 1, + "minValue": 1, + "maxValue": 1000, + "metadata": { + "description": "Optional. Number of nodes to use in the cluster. Allowed values: 1 for the Basic SKU tier and 2-1000 for Standard. Default: 1 for dev/test SKUs, 2 for standard SKUs." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Azure location to use for the managed identity and deployment script to auto-start triggers. Default: (resource group location)." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to all resources." + } + }, + "tagsByResource": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." + } + }, + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory instance." + } + }, + "rawRetentionInDays": { + "type": "int", + "defaultValue": 0, + "metadata": { + "description": "Optional. Number of days of data to retain in the Data Explorer *_raw tables. Default: 0." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account to use for data ingestion." + } + }, + "virtualNetworkId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the virtual network for private endpoints." + } + }, + "privateEndpointSubnetId": { + "type": "string", + "metadata": { + "description": "Required. Resource ID of the subnet for private endpoints." + } + }, + "enablePublicAccess": { + "type": "bool", + "metadata": { + "description": "Optional. Enable public access." + } + } + }, + "variables": { + "$fxv#0": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_1(id: string) {\r\n dynamic({\r\n \"arizeai.observabilityeval/organizations\": { \"SingularDisplayName\": \"Azure Native Arize AI Cloud Service\" }\r\n ,\"astronomer.astro/organizations\": { \"SingularDisplayName\": \"Astro Organization\" }\r\n ,\"citrix.services/xenappessentials\": { \"SingularDisplayName\": \"Citrix Virtual Apps Essentials\" }\r\n ,\"citrix.services/xendesktopessentials\": { \"SingularDisplayName\": \"Citrix Virtual Desktops Essentials\" }\r\n ,\"commvault.contentstore/cloudaccounts\": { \"SingularDisplayName\": \"Commvault Cloud Account\" }\r\n ,\"commvault.contentstore/cloudaccounts/plans\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts plan\" }\r\n ,\"commvault.contentstore/cloudaccounts/protectiongroups\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection group\" }\r\n ,\"commvault.contentstore/cloudaccounts/protectiongroups/protecteditems\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts protection groups protected item\" }\r\n ,\"commvault.contentstore/cloudaccounts/storages\": { \"SingularDisplayName\": \"Commvault.ContentStore cloud accounts storage\" }\r\n ,\"dell.storage/filesystems\": { \"SingularDisplayName\": \"Dell PowerScale\" }\r\n ,\"dynatrace.observability/monitors\": { \"SingularDisplayName\": \"Dynatrace\" }\r\n ,\"github.network/networksettings\": { \"SingularDisplayName\": \"GitHub.Network network setting\" }\r\n ,\"informatica.datamanagement/organizations\": { \"SingularDisplayName\": \"Informatica Organization\" }\r\n ,\"lambdatest.hyperexecute/organizations\": { \"SingularDisplayName\": \"Azure Native LambdaTest - HyperExecute Cloud Service\" }\r\n ,\"microsoft.aad/domainservices\": { \"SingularDisplayName\": \"Microsoft Entra Domain Services\" }\r\n ,\"microsoft.aadiam/diagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.aadiam diagnostic setting\" }\r\n ,\"microsoft.aadiam/privatelinkforazuread\": { \"SingularDisplayName\": \"Private Link for Microsoft Entra ID\" }\r\n ,\"microsoft.advisor/advisorscore\": { \"SingularDisplayName\": \"Microsoft.Advisor advisor score\" }\r\n ,\"microsoft.advisor/assessments\": { \"SingularDisplayName\": \"Microsoft.Advisor assessment\" }\r\n ,\"microsoft.advisor/configurations\": { \"SingularDisplayName\": \"Microsoft.Advisor configuration\" }\r\n ,\"microsoft.advisor/generaterecommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor generate recommendation\" }\r\n ,\"microsoft.advisor/metadata\": { \"SingularDisplayName\": \"Microsoft.Advisor metadata\" }\r\n ,\"microsoft.advisor/recommendations\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendation\" }\r\n ,\"microsoft.advisor/recommendations/suppressions\": { \"SingularDisplayName\": \"Microsoft.Advisor recommendations suppression\" }\r\n ,\"microsoft.advisor/resiliencyreviews\": { \"SingularDisplayName\": \"Microsoft.Advisor resiliency review\" }\r\n ,\"microsoft.agfoodplatform/farmbeats\": { \"SingularDisplayName\": \"Azure Data Manager for Agriculture\" }\r\n ,\"microsoft.agfoodplatform/farmbeatsextensiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats extension definition\" }\r\n ,\"microsoft.agfoodplatform/farmbeatssolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.AgFoodPlatform farm beats solution definition\" }\r\n ,\"microsoft.agricultureplatform/agriservices\": { \"SingularDisplayName\": \"Agriculture data solutions\" }\r\n ,\"microsoft.akshybrid/agentpools\": { \"SingularDisplayName\": \"Microsoft.AksHybrid agent pool\" }\r\n ,\"microsoft.akshybrid/provisionedclusters\": { \"SingularDisplayName\": \"Microsoft.AksHybrid provisioned cluster\" }\r\n ,\"microsoft.akshybrid/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.AksHybrid upgrade profile\" }\r\n ,\"microsoft.alertsmanagement/actionrules\": { \"SingularDisplayName\": \"Alert processing rule\" }\r\n ,\"microsoft.alertsmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alert\" }\r\n ,\"microsoft.alertsmanagement/alerts/enrichments\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement alerts enrichment\" }\r\n ,\"microsoft.alertsmanagement/prometheusrulegroups\": { \"SingularDisplayName\": \"Prometheus rule group\" }\r\n ,\"microsoft.alertsmanagement/smartdetectoralertrules\": { \"SingularDisplayName\": \"Smart detector alert rule\" }\r\n ,\"microsoft.alertsmanagement/smartgroups\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement smart group\" }\r\n ,\"microsoft.alertsmanagement/tenantactivitylogalerts\": { \"SingularDisplayName\": \"Microsoft.AlertsManagement tenant activity log alert\" }\r\n ,\"microsoft.all/arcvirtualmachines\": { \"SingularDisplayName\": \"Azure Arc virtual machine\" }\r\n ,\"microsoft.all/hcivirtualmachines\": { \"SingularDisplayName\": \"Azure Local Virtual Machine - Azure Arc\" }\r\n ,\"microsoft.all/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.analysisservices/servers\": { \"SingularDisplayName\": \"Analysis Services server\" }\r\n ,\"microsoft.anybuild/clusters\": { \"SingularDisplayName\": \"AnyBuild cluster\" }\r\n ,\"microsoft.apicenter/deletedservices\": { \"SingularDisplayName\": \"Microsoft.ApiCenter deleted service\" }\r\n ,\"microsoft.apicenter/services\": { \"SingularDisplayName\": \"API Center\" }\r\n ,\"microsoft.apicenter/services/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.apimanagement/gateways\": { \"SingularDisplayName\": \"API Management gateway\" }\r\n ,\"microsoft.apimanagement/gateways/configconnections\": { \"SingularDisplayName\": \"Microsoft.ApiManagement gateways config connection\" }\r\n ,\"microsoft.apimanagement/service\": { \"SingularDisplayName\": \"API Management service\" }\r\n ,\"microsoft.apimanagement/service/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.apisecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.ApiSecurity defender setting\" }\r\n ,\"microsoft.app/agents\": { \"SingularDisplayName\": \"SRE Agent\" }\r\n ,\"microsoft.app/builders\": { \"SingularDisplayName\": \"Microsoft.App builder\" }\r\n ,\"microsoft.app/builders/builds\": { \"SingularDisplayName\": \"Microsoft.App builders build\" }\r\n ,\"microsoft.app/connectedenvironments\": { \"SingularDisplayName\": \"Container Apps Connected Environment\" }\r\n ,\"microsoft.app/containerapps\": { \"SingularDisplayName\": \"Container App\" }\r\n ,\"microsoft.app/jobs\": { \"SingularDisplayName\": \"Container App Job\" }\r\n ,\"microsoft.app/logicapps\": { \"SingularDisplayName\": \"Logic app\" }\r\n ,\"microsoft.app/logicapps/workflows\": { \"SingularDisplayName\": \"Logic app workflow\" }\r\n ,\"microsoft.app/managedenvironments\": { \"SingularDisplayName\": \"Container Apps Environment\" }\r\n ,\"microsoft.app/sessionpools\": { \"SingularDisplayName\": \"Container App Session Pool\" }\r\n ,\"microsoft.app/spaces\": { \"SingularDisplayName\": \"App Space\" }\r\n ,\"microsoft.appassessment/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate project\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessment\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedapplications\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed application\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments assessed machine\" }\r\n ,\"microsoft.appassessment/migrateprojects/assessments/machinestoassess\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects assessments machines to asses\" }\r\n ,\"microsoft.appassessment/migrateprojects/sites\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects site\" }\r\n ,\"microsoft.appassessment/migrateprojects/sites/applianceconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppAssessment migrate projects sites appliance configuration\" }\r\n ,\"microsoft.appcomplianceautomation/reports\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation report\" }\r\n ,\"microsoft.appcomplianceautomation/reports/evidences\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports evidence\" }\r\n ,\"microsoft.appcomplianceautomation/reports/scopingconfigurations\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports scoping configuration\" }\r\n ,\"microsoft.appcomplianceautomation/reports/snapshots\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshot\" }\r\n ,\"microsoft.appcomplianceautomation/reports/snapshots/controls\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports snapshots control\" }\r\n ,\"microsoft.appcomplianceautomation/reports/webhooks\": { \"SingularDisplayName\": \"Microsoft.AppComplianceAutomation reports webhook\" }\r\n ,\"microsoft.appconfiguration/configurationstores\": { \"SingularDisplayName\": \"App Configuration\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hub\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs/applications\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs application\" }\r\n ,\"microsoft.applicationmigration/discoveryhubs/applications/members\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration discovery hubs applications member\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsite\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites agent\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqldatabases\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqldatabase\" }\r\n ,\"microsoft.applicationmigration/pgsqlsites/pgsqlinstances\": { \"SingularDisplayName\": \"Microsoft.ApplicationMigration pgsqlsites pgsqlinstance\" }\r\n ,\"microsoft.appplatform/spring\": { \"SingularDisplayName\": \"Azure Spring Apps\" }\r\n ,\"microsoft.appsecurity/appprotectmanagedrulesetmanifests\": { \"SingularDisplayName\": \"Microsoft.AppSecurity app protect managed rule set manifest\" }\r\n ,\"microsoft.appsecurity/policies\": { \"SingularDisplayName\": \"App Protect Policy\" }\r\n ,\"microsoft.arc/all\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\r\n ,\"microsoft.arc/allfairfax\": { \"SingularDisplayName\": \"Azure Arc enabled resource\" }\r\n ,\"microsoft.arc/kubernetesresources\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\r\n ,\"microsoft.arc/kubernetesresourcesfairfax\": { \"SingularDisplayName\": \"Azure Arc Kubernetes cluster\" }\r\n ,\"microsoft.arcnetworking/arcnwloadbalancers\": { \"SingularDisplayName\": \"Microsoft.ArcNetworking arc nw load balancer\" }\r\n ,\"microsoft.aszlabhardware/labservers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware labserver\" }\r\n ,\"microsoft.aszlabhardware/reservations\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservation\" }\r\n ,\"microsoft.aszlabhardware/reservations/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware reservations server\" }\r\n ,\"microsoft.aszlabhardware/servers\": { \"SingularDisplayName\": \"Microsoft.AszLabHardware server\" }\r\n ,\"microsoft.attestation/attestationproviders\": { \"SingularDisplayName\": \"Attestation provider\" }\r\n ,\"microsoft.authorization/accessreviewhistorydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review history definition\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definition\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instance\" }\r\n ,\"microsoft.authorization/accessreviewscheduledefinitions/instances/decisions\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule definitions instances decision\" }\r\n ,\"microsoft.authorization/accessreviewschedulesettings\": { \"SingularDisplayName\": \"Microsoft.Authorization access review schedule setting\" }\r\n ,\"microsoft.authorization/datapolicymanifests\": { \"SingularDisplayName\": \"Microsoft.Authorization data policy manifest\" }\r\n ,\"microsoft.authorization/denyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization deny assignment\" }\r\n ,\"microsoft.authorization/locks\": { \"SingularDisplayName\": \"Microsoft.Authorization lock\" }\r\n ,\"microsoft.authorization/policyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization policy assignment\" }\r\n ,\"microsoft.authorization/policydefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definition\" }\r\n ,\"microsoft.authorization/policydefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy definitions version\" }\r\n ,\"microsoft.authorization/policyexemptions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy exemption\" }\r\n ,\"microsoft.authorization/policysetdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definition\" }\r\n ,\"microsoft.authorization/policysetdefinitions/versions\": { \"SingularDisplayName\": \"Microsoft.Authorization policy set definitions version\" }\r\n ,\"microsoft.authorization/privatelinkassociations\": { \"SingularDisplayName\": \"Microsoft.Authorization private link association\" }\r\n ,\"microsoft.authorization/provideroperations\": { \"SingularDisplayName\": \"Microsoft.Authorization provider operation\" }\r\n ,\"microsoft.authorization/resourcemanagementprivatelinks\": { \"SingularDisplayName\": \"Resource management private link\" }\r\n ,\"microsoft.authorization/roleassignmentapprovals\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approval\" }\r\n ,\"microsoft.authorization/roleassignmentapprovals/stages\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment approvals stage\" }\r\n ,\"microsoft.authorization/roleassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment\" }\r\n ,\"microsoft.authorization/roleassignmentscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule instance\" }\r\n ,\"microsoft.authorization/roleassignmentschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule request\" }\r\n ,\"microsoft.authorization/roleassignmentschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role assignment schedule\" }\r\n ,\"microsoft.authorization/roledefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role definition\" }\r\n ,\"microsoft.authorization/roleeligibilityscheduleinstances\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule instance\" }\r\n ,\"microsoft.authorization/roleeligibilityschedulerequests\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule request\" }\r\n ,\"microsoft.authorization/roleeligibilityschedules\": { \"SingularDisplayName\": \"Microsoft.Authorization role eligibility schedule\" }\r\n ,\"microsoft.authorization/rolemanagementalertconfigurations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert configuration\" }\r\n ,\"microsoft.authorization/rolemanagementalertdefinitions\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert definition\" }\r\n ,\"microsoft.authorization/rolemanagementalertoperations\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert operation\" }\r\n ,\"microsoft.authorization/rolemanagementalerts\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alert\" }\r\n ,\"microsoft.authorization/rolemanagementalerts/alertincidents\": { \"SingularDisplayName\": \"Microsoft.Authorization role management alerts alert incident\" }\r\n ,\"microsoft.authorization/rolemanagementpolicies\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy\" }\r\n ,\"microsoft.authorization/rolemanagementpolicyassignments\": { \"SingularDisplayName\": \"Microsoft.Authorization role management policy assignment\" }\r\n ,\"microsoft.automanage/bestpractices\": { \"SingularDisplayName\": \"Microsoft.Automanage best practice\" }\r\n ,\"microsoft.automanage/bestpractices/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage best practices version\" }\r\n ,\"microsoft.automanage/configurationprofileassignments\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignment\" }\r\n ,\"microsoft.automanage/configurationprofileassignments/reports\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile assignments report\" }\r\n ,\"microsoft.automanage/configurationprofiles\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profile\" }\r\n ,\"microsoft.automanage/configurationprofiles/versions\": { \"SingularDisplayName\": \"Microsoft.Automanage configuration profiles version\" }\r\n ,\"microsoft.automanage/serviceprincipals\": { \"SingularDisplayName\": \"ServicePrincipals\" }\r\n ,\"microsoft.automation/automationaccounts\": { \"SingularDisplayName\": \"Automation account\" }\r\n ,\"microsoft.automation/automationaccounts/hybridrunbookworkergroups\": { \"SingularDisplayName\": \"Automation hybrid worker group\" }\r\n ,\"microsoft.automation/automationaccounts/runbooks\": { \"SingularDisplayName\": \"Automation runbook\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform account\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/accounts/datapools\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform accounts data pool\" }\r\n ,\"microsoft.autonomousdevelopmentplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.AutonomousDevelopmentPlatform workspace\" }\r\n ,\"microsoft.avs/privateclouds\": { \"SingularDisplayName\": \"Azure VMware Solution private cloud\" }\r\n ,\"microsoft.awsconnector/accessanalyzeranalyzers\": { \"SingularDisplayName\": \"Access Analyzer Analyzer\" }\r\n ,\"microsoft.awsconnector/acmcertificatesummaries\": { \"SingularDisplayName\": \"ACM Certificate Summary\" }\r\n ,\"microsoft.awsconnector/apigatewayrestapis\": { \"SingularDisplayName\": \"Api Gateway Rest Api\" }\r\n ,\"microsoft.awsconnector/apigatewaystages\": { \"SingularDisplayName\": \"Api Gateway Stage\" }\r\n ,\"microsoft.awsconnector/applicationautoscalingscalabletargets\": { \"SingularDisplayName\": \"Application Auto Scaling Scalable Target\" }\r\n ,\"microsoft.awsconnector/appsyncgraphqlapis\": { \"SingularDisplayName\": \"App Sync Graphql Api\" }\r\n ,\"microsoft.awsconnector/autoscalingautoscalinggroups\": { \"SingularDisplayName\": \"Auto Scaling Auto Scaling Group\" }\r\n ,\"microsoft.awsconnector/cloudformationstacks\": { \"SingularDisplayName\": \"Cloud Formation Stack\" }\r\n ,\"microsoft.awsconnector/cloudformationstacksets\": { \"SingularDisplayName\": \"Cloud Formation Stack Set\" }\r\n ,\"microsoft.awsconnector/cloudfrontdistributions\": { \"SingularDisplayName\": \"Cloud Front Distribution\" }\r\n ,\"microsoft.awsconnector/cloudtrailtrails\": { \"SingularDisplayName\": \"Cloud Trail Trail\" }\r\n ,\"microsoft.awsconnector/cloudwatchalarms\": { \"SingularDisplayName\": \"Cloud Watch Alarm\" }\r\n ,\"microsoft.awsconnector/codebuildprojects\": { \"SingularDisplayName\": \"Code Build Project\" }\r\n ,\"microsoft.awsconnector/codebuildsourcecredentialsinfos\": { \"SingularDisplayName\": \"Code Build Source Credentials Info\" }\r\n ,\"microsoft.awsconnector/configserviceconfigurationrecorders\": { \"SingularDisplayName\": \"Config Service Configuration Recorder\" }\r\n ,\"microsoft.awsconnector/configserviceconfigurationrecorderstatuses\": { \"SingularDisplayName\": \"Config Service Configuration Recorder Status\" }\r\n ,\"microsoft.awsconnector/configservicedeliverychannels\": { \"SingularDisplayName\": \"Config Service Delivery Channel\" }\r\n ,\"microsoft.awsconnector/databasemigrationservicereplicationinstances\": { \"SingularDisplayName\": \"Database Migration Service Replication Instance\" }\r\n ,\"microsoft.awsconnector/daxclusters\": { \"SingularDisplayName\": \"DAX Cluster\" }\r\n ,\"microsoft.awsconnector/dynamodbcontinuousbackupsdescriptions\": { \"SingularDisplayName\": \"Dynamo DB Continuous Backups Description\" }\r\n ,\"microsoft.awsconnector/dynamodbtables\": { \"SingularDisplayName\": \"Dynamo DB Table\" }\r\n ,\"microsoft.awsconnector/ec2accountattributes\": { \"SingularDisplayName\": \"EC2 Account Attribute\" }\r\n ,\"microsoft.awsconnector/ec2addresses\": { \"SingularDisplayName\": \"EC2 Address\" }\r\n ,\"microsoft.awsconnector/ec2flowlogs\": { \"SingularDisplayName\": \"EC2 Flow Log\" }\r\n ,\"microsoft.awsconnector/ec2images\": { \"SingularDisplayName\": \"EC2 Image\" }\r\n ,\"microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\r\n ,\"microsoft.awsconnector/ec2instancestatuses\": { \"SingularDisplayName\": \"EC2 Instance Status\" }\r\n ,\"microsoft.awsconnector/ec2ipams\": { \"SingularDisplayName\": \"EC2 Ipam\" }\r\n ,\"microsoft.awsconnector/ec2keypairs\": { \"SingularDisplayName\": \"EC2 Key Pair\" }\r\n ,\"microsoft.awsconnector/ec2networkacls\": { \"SingularDisplayName\": \"EC2 Network Acl\" }\r\n ,\"microsoft.awsconnector/ec2networkinterfaces\": { \"SingularDisplayName\": \"EC2 Network Interface\" }\r\n ,\"microsoft.awsconnector/ec2routetables\": { \"SingularDisplayName\": \"EC2 Route Table\" }\r\n ,\"microsoft.awsconnector/ec2securitygroups\": { \"SingularDisplayName\": \"EC2 Security Group\" }\r\n ,\"microsoft.awsconnector/ec2snapshots\": { \"SingularDisplayName\": \"EC2 Snapshot\" }\r\n ,\"microsoft.awsconnector/ec2subnets\": { \"SingularDisplayName\": \"EC2 Subnet\" }\r\n ,\"microsoft.awsconnector/ec2volumes\": { \"SingularDisplayName\": \"EC2 Volume\" }\r\n ,\"microsoft.awsconnector/ec2vpcendpoints\": { \"SingularDisplayName\": \"EC2 VPCEndpoint\" }\r\n ,\"microsoft.awsconnector/ec2vpcpeeringconnections\": { \"SingularDisplayName\": \"EC2 VPCPeering Connection\" }\r\n ,\"microsoft.awsconnector/ec2vpcs\": { \"SingularDisplayName\": \"EC2 VPC\" }\r\n ,\"microsoft.awsconnector/ecrimagedetails\": { \"SingularDisplayName\": \"ECR Image Detail\" }\r\n ,\"microsoft.awsconnector/ecrrepositories\": { \"SingularDisplayName\": \"ECR Repository\" }\r\n ,\"microsoft.awsconnector/ecsclusters\": { \"SingularDisplayName\": \"ECS Cluster\" }\r\n ,\"microsoft.awsconnector/ecsservices\": { \"SingularDisplayName\": \"ECS Service\" }\r\n ,\"microsoft.awsconnector/ecstaskdefinitions\": { \"SingularDisplayName\": \"ECS Task Definition\" }\r\n ,\"microsoft.awsconnector/efsfilesystems\": { \"SingularDisplayName\": \"EFS File System\" }\r\n ,\"microsoft.awsconnector/efsmounttargets\": { \"SingularDisplayName\": \"EFS Mount Target\" }\r\n ,\"microsoft.awsconnector/eksnodegroups\": { \"SingularDisplayName\": \"EKS Nodegroup\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkapplications\": { \"SingularDisplayName\": \"Elastic Beanstalk Application\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkconfigurationtemplates\": { \"SingularDisplayName\": \"Elastic Beanstalk Configuration Template\" }\r\n ,\"microsoft.awsconnector/elasticbeanstalkenvironments\": { \"SingularDisplayName\": \"Elastic Beanstalk Environment\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2listeners\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Listener\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2loadbalancers\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Load Balancer\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2targetgroups\": { \"SingularDisplayName\": \"Elastic Load Balancing V2 Target Group\" }\r\n ,\"microsoft.awsconnector/elasticloadbalancingv2targethealthdescriptions\": { \"SingularDisplayName\": \"Elastic Load Balancing v2 Target Health Description\" }\r\n ,\"microsoft.awsconnector/elasticsearchdomains\": { \"SingularDisplayName\": \"Elasticsearch Domain\" }\r\n ,\"microsoft.awsconnector/emrclusters\": { \"SingularDisplayName\": \"EMR Cluster\" }\r\n ,\"microsoft.awsconnector/guarddutydetectors\": { \"SingularDisplayName\": \"Guard Duty Detector\" }\r\n ,\"microsoft.awsconnector/iamaccesskeylastuseds\": { \"SingularDisplayName\": \"IAM Access Key Last Used\" }\r\n ,\"microsoft.awsconnector/iamaccesskeymetadata\": { \"SingularDisplayName\": \"IAM Access Key Metadata\" }\r\n ,\"microsoft.awsconnector/iamgroups\": { \"SingularDisplayName\": \"IAM Group\" }\r\n ,\"microsoft.awsconnector/iaminstanceprofiles\": { \"SingularDisplayName\": \"IAM Instance Profile\" }\r\n ,\"microsoft.awsconnector/iammanagedpolicies\": { \"SingularDisplayName\": \"IAM Managed Policy\" }\r\n ,\"microsoft.awsconnector/iammfadevices\": { \"SingularDisplayName\": \"IAM MFADevice\" }\r\n ,\"microsoft.awsconnector/iampasswordpolicies\": { \"SingularDisplayName\": \"IAM Password Policy\" }\r\n ,\"microsoft.awsconnector/iampolicyversions\": { \"SingularDisplayName\": \"IAM Policy Version\" }\r\n ,\"microsoft.awsconnector/iamroles\": { \"SingularDisplayName\": \"IAM Role\" }\r\n ,\"microsoft.awsconnector/iamservercertificates\": { \"SingularDisplayName\": \"IAM Server Certificate\" }\r\n ,\"microsoft.awsconnector/iamuserpolicies\": { \"SingularDisplayName\": \"IAM User Policy\" }\r\n ,\"microsoft.awsconnector/iamvirtualmfadevices\": { \"SingularDisplayName\": \"IAM Virtual MFADevice\" }\r\n ,\"microsoft.awsconnector/kmsaliases\": { \"SingularDisplayName\": \"KMS Alias\" }\r\n ,\"microsoft.awsconnector/kmskeys\": { \"SingularDisplayName\": \"KMS Key\" }\r\n ,\"microsoft.awsconnector/lambdafunctioncodelocations\": { \"SingularDisplayName\": \"Lambda Function Code Location\" }\r\n ,\"microsoft.awsconnector/lambdafunctionconfigurations\": { \"SingularDisplayName\": \"Microsoft.AwsConnector lambda function configuration\" }\r\n ,\"microsoft.awsconnector/lambdafunctions\": { \"SingularDisplayName\": \"Lambda Function\" }\r\n ,\"microsoft.awsconnector/licensemanagerlicenses\": { \"SingularDisplayName\": \"License Manager License\" }\r\n ,\"microsoft.awsconnector/lightsailbuckets\": { \"SingularDisplayName\": \"Lightsail Bucket\" }\r\n ,\"microsoft.awsconnector/lightsailinstances\": { \"SingularDisplayName\": \"Lightsail Instance\" }\r\n ,\"microsoft.awsconnector/logsloggroups\": { \"SingularDisplayName\": \"Logs Log Group\" }\r\n ,\"microsoft.awsconnector/logslogstreams\": { \"SingularDisplayName\": \"Logs Log Stream\" }\r\n ,\"microsoft.awsconnector/logsmetricfilters\": { \"SingularDisplayName\": \"Logs Metric Filter\" }\r\n ,\"microsoft.awsconnector/logssubscriptionfilters\": { \"SingularDisplayName\": \"Logs Subscription Filter\" }\r\n ,\"microsoft.awsconnector/macie2jobsummaries\": { \"SingularDisplayName\": \"Macie2 Job Summary\" }\r\n ,\"microsoft.awsconnector/macieallowlists\": { \"SingularDisplayName\": \"Macie Allow List\" }\r\n ,\"microsoft.awsconnector/networkfirewallfirewallpolicies\": { \"SingularDisplayName\": \"Network Firewall Firewall Policy\" }\r\n ,\"microsoft.awsconnector/networkfirewallfirewalls\": { \"SingularDisplayName\": \"Network Firewall Firewall\" }\r\n ,\"microsoft.awsconnector/networkfirewallrulegroups\": { \"SingularDisplayName\": \"Network Firewall Rule Group\" }\r\n ,\"microsoft.awsconnector/opensearchdomainstatuses\": { \"SingularDisplayName\": \"Open Search Domain Status\" }\r\n ,\"microsoft.awsconnector/opensearchservicedomains\": { \"SingularDisplayName\": \"Open Search Service Domain\" }\r\n ,\"microsoft.awsconnector/organizationsaccounts\": { \"SingularDisplayName\": \"Organizations Account\" }\r\n ,\"microsoft.awsconnector/organizationsorganizations\": { \"SingularDisplayName\": \"Organizations Organization\" }\r\n ,\"microsoft.awsconnector/rdsdbclusters\": { \"SingularDisplayName\": \"RDS DBCluster\" }\r\n ,\"microsoft.awsconnector/rdsdbinstances\": { \"SingularDisplayName\": \"RDS DBInstance\" }\r\n ,\"microsoft.awsconnector/rdsdbsnapshotattributesresults\": { \"SingularDisplayName\": \"RDS DBSnapshot Attributes Result\" }\r\n ,\"microsoft.awsconnector/rdsdbsnapshots\": { \"SingularDisplayName\": \"RDS DBSnapshot\" }\r\n ,\"microsoft.awsconnector/rdseventsubscriptions\": { \"SingularDisplayName\": \"RDS Event Subscription\" }\r\n ,\"microsoft.awsconnector/rdsexporttasks\": { \"SingularDisplayName\": \"RDS Export Task\" }\r\n ,\"microsoft.awsconnector/redshiftclusterparametergroups\": { \"SingularDisplayName\": \"Redshift Cluster Parameter Group\" }\r\n ,\"microsoft.awsconnector/redshiftclusters\": { \"SingularDisplayName\": \"Redshift Cluster\" }\r\n ,\"microsoft.awsconnector/route53domainsdomainsummaries\": { \"SingularDisplayName\": \"Route 53 Domains Domain Summary\" }\r\n ,\"microsoft.awsconnector/route53hostedzones\": { \"SingularDisplayName\": \"Route53 Hosted Zone\" }\r\n ,\"microsoft.awsconnector/route53resourcerecordsets\": { \"SingularDisplayName\": \"Route 53 Resource Record Set\" }\r\n ,\"microsoft.awsconnector/s3accesscontrolpolicies\": { \"SingularDisplayName\": \"S3 Access Control Policy\" }\r\n ,\"microsoft.awsconnector/s3accesspoints\": { \"SingularDisplayName\": \"S3 Access Point\" }\r\n ,\"microsoft.awsconnector/s3bucketpolicies\": { \"SingularDisplayName\": \"S3 Bucket Policy\" }\r\n ,\"microsoft.awsconnector/s3buckets\": { \"SingularDisplayName\": \"S3 Bucket\" }\r\n ,\"microsoft.awsconnector/s3controlmultiregionaccesspointpolicydocuments\": { \"SingularDisplayName\": \"S3 Control Multi Region Access Point Policy Document\" }\r\n ,\"microsoft.awsconnector/sagemakerapps\": { \"SingularDisplayName\": \"Sage Maker App\" }\r\n ,\"microsoft.awsconnector/sagemakerdevices\": { \"SingularDisplayName\": \"Sage Maker Device\" }\r\n ,\"microsoft.awsconnector/sagemakerimages\": { \"SingularDisplayName\": \"Sage Maker Image\" }\r\n ,\"microsoft.awsconnector/sagemakernotebookinstancesummaries\": { \"SingularDisplayName\": \"Sage Maker Notebook Instance Summary\" }\r\n ,\"microsoft.awsconnector/secretsmanagerresourcepolicies\": { \"SingularDisplayName\": \"Secrets Manager Resource Policy\" }\r\n ,\"microsoft.awsconnector/secretsmanagersecrets\": { \"SingularDisplayName\": \"Secrets Manager Secret\" }\r\n ,\"microsoft.awsconnector/snssubscriptions\": { \"SingularDisplayName\": \"SNS Subscription\" }\r\n ,\"microsoft.awsconnector/snstopics\": { \"SingularDisplayName\": \"SNS Topic\" }\r\n ,\"microsoft.awsconnector/sqsqueues\": { \"SingularDisplayName\": \"SQS Queue\" }\r\n ,\"microsoft.awsconnector/ssminstanceinformations\": { \"SingularDisplayName\": \"SSM Instance Information\" }\r\n ,\"microsoft.awsconnector/ssmparameters\": { \"SingularDisplayName\": \"SSM Parameter\" }\r\n ,\"microsoft.awsconnector/ssmresourcecompliancesummaryitems\": { \"SingularDisplayName\": \"SSM Resource Compliance Summary Item\" }\r\n ,\"microsoft.awsconnector/wafv2ipsets\": { \"SingularDisplayName\": \"WAFv2 IPSet\" }\r\n ,\"microsoft.awsconnector/wafv2loggingconfigurations\": { \"SingularDisplayName\": \"WAFv2 Logging Configuration\" }\r\n ,\"microsoft.awsconnector/wafv2webaclassociations\": { \"SingularDisplayName\": \"WAFv2 Web ACLAssociation\" }\r\n ,\"microsoft.awsconnector/wafwebaclsummaries\": { \"SingularDisplayName\": \"WAF Web ACLSummary\" }\r\n ,\"microsoft.azureactivedirectory/b2cdirectories\": { \"SingularDisplayName\": \"B2C tenant\" }\r\n ,\"microsoft.azureactivedirectory/ciamdirectories\": { \"SingularDisplayName\": \"External Configuration Tenant\" }\r\n ,\"microsoft.azureactivedirectory/guestusages\": { \"SingularDisplayName\": \"Guest Usage\" }\r\n ,\"microsoft.azurearcdata/datacontrollers\": { \"SingularDisplayName\": \"Azure Arc data controller\" }\r\n ,\"microsoft.azurearcdata/mysqlserver\": { \"SingularDisplayName\": \"MySql Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/postgresinstances\": { \"SingularDisplayName\": \"PostgreSQL server ? Azure Arc\" }\r\n ,\"microsoft.azurearcdata/postgressqlserver\": { \"SingularDisplayName\": \"PostgresSql Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlmanagedinstances\": { \"SingularDisplayName\": \"SQL managed instance - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserveresulicenses\": { \"SingularDisplayName\": \"SQL Server ESU license\" }\r\n ,\"microsoft.azurearcdata/sqlserverinstances\": { \"SingularDisplayName\": \"SQL Server - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserverinstances/databases\": { \"SingularDisplayName\": \"SQL Server database - Azure Arc\" }\r\n ,\"microsoft.azurearcdata/sqlserverlicenses\": { \"SingularDisplayName\": \"SQL Server License\" }\r\n ,\"microsoft.azurebusinesscontinuity/deletedunifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity deleted unified protected item\" }\r\n ,\"microsoft.azurebusinesscontinuity/unifiedprotecteditems\": { \"SingularDisplayName\": \"Microsoft.AzureBusinessContinuity unified protected item\" }\r\n ,\"microsoft.azurecis/aadapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis AAD application\" }\r\n ,\"microsoft.azurecis/addressrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis address record\" }\r\n ,\"microsoft.azurecis/autopilotenvironments\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot environment\" }\r\n ,\"microsoft.azurecis/autopilotmachinefunctions\": { \"SingularDisplayName\": \"Microsoft.AzureCis autopilot machine function\" }\r\n ,\"microsoft.azurecis/autopilotsoftwareloadbalancevirtualips\": { \"SingularDisplayName\": \"Microsoft.AzureCis auto pilot software load balance virtual IP\" }\r\n ,\"microsoft.azurecis/azcopies\": { \"SingularDisplayName\": \"Microsoft.AzureCis az copy\" }\r\n ,\"microsoft.azurecis/canonicalnamerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis canonical name record\" }\r\n ,\"microsoft.azurecis/dsmsallowlists\": { \"SingularDisplayName\": \"Microsoft.AzureCis ds msallowlist\" }\r\n ,\"microsoft.azurecis/dsmscertificates\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms certificate\" }\r\n ,\"microsoft.azurecis/dsmsrootfolders\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsms root folder\" }\r\n ,\"microsoft.azurecis/dstsapplications\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts application\" }\r\n ,\"microsoft.azurecis/dstsserviceaccounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service account\" }\r\n ,\"microsoft.azurecis/dstsserviceclientidentities\": { \"SingularDisplayName\": \"Microsoft.AzureCis dsts service client identity\" }\r\n ,\"microsoft.azurecis/genericgenevaactions\": { \"SingularDisplayName\": \"Microsoft.AzureCis generic geneva action\" }\r\n ,\"microsoft.azurecis/plannedquotas\": { \"SingularDisplayName\": \"Microsoft.AzureCis planned quota\" }\r\n ,\"microsoft.azurecis/pointerrecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis pointer record\" }\r\n ,\"microsoft.azurecis/publishconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis publish config value\" }\r\n ,\"microsoft.azurecis/pushagentv2accounts\": { \"SingularDisplayName\": \"Microsoft.AzureCis push agent v2 account\" }\r\n ,\"microsoft.azurecis/servicerecords\": { \"SingularDisplayName\": \"Microsoft.AzureCis service record\" }\r\n ,\"microsoft.azurecis/sharedconfigvalues\": { \"SingularDisplayName\": \"Microsoft.AzureCis shared config value\" }\r\n ,\"microsoft.azurecloudmetadata/clouds\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata cloud\" }\r\n ,\"microsoft.azurecloudmetadata/clouds/geographies\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geography\" }\r\n ,\"microsoft.azurecloudmetadata/clouds/geographies/regions\": { \"SingularDisplayName\": \"Microsoft.AzureCloudMetadata clouds geographies region\" }\r\n ,\"microsoft.azuredatatransfer/connections\": { \"SingularDisplayName\": \"Connection\" }\r\n ,\"microsoft.azuredatatransfer/connections/flows\": { \"SingularDisplayName\": \"Flow\" }\r\n ,\"microsoft.azuredatatransfer/pipelines\": { \"SingularDisplayName\": \"Pipeline\" }\r\n ,\"microsoft.azurefleet/fleets\": { \"SingularDisplayName\": \"Compute Fleet\" }\r\n ,\"microsoft.azurefleet/fleetscomputehub\": { \"SingularDisplayName\": \"Compute Fleet\" }\r\n ,\"microsoft.azureimagetestingforlinux/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job\" }\r\n ,\"microsoft.azureimagetestingforlinux/jobtemplates\": { \"SingularDisplayName\": \"Microsoft.AzureImageTestingForLinux job template\" }\r\n ,\"microsoft.azurelargeinstance/azurelargeinstances\": { \"SingularDisplayName\": \"Azure Large Instance\" }\r\n ,\"microsoft.azurelargeinstance/azurelargestorageinstances\": { \"SingularDisplayName\": \"Microsoft.AzureLargeInstance Azure large storage instance\" }\r\n ,\"microsoft.azurepercept/accounts\": { \"SingularDisplayName\": \"Microsoft.AzurePercept account\" }\r\n ,\"microsoft.azurepercept/accounts/devices\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts device\" }\r\n ,\"microsoft.azurepercept/accounts/devices/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts devices sensor\" }\r\n ,\"microsoft.azurepercept/accounts/sensors\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts sensor\" }\r\n ,\"microsoft.azurepercept/accounts/solutioninstances\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solutioninstance\" }\r\n ,\"microsoft.azurepercept/accounts/solutions\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts solution\" }\r\n ,\"microsoft.azurepercept/accounts/targets\": { \"SingularDisplayName\": \"Microsoft.AzurePercept accounts target\" }\r\n ,\"microsoft.azureplaywrightservice/accounts\": { \"SingularDisplayName\": \"Playwright Testing\" }\r\n ,\"microsoft.azurescan/scanningaccounts\": { \"SingularDisplayName\": \"ESRP Scan\" }\r\n ,\"microsoft.azuresphere/catalogs\": { \"SingularDisplayName\": \"Azure Sphere Catalog\" }\r\n ,\"microsoft.azurespherev2/catalogs\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalog\" }\r\n ,\"microsoft.azurespherev2/catalogs/artifacts\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs artifact\" }\r\n ,\"microsoft.azurespherev2/catalogs/certificates\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs certificate\" }\r\n ,\"microsoft.azurespherev2/catalogs/deviceregistrations\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs device registration\" }\r\n ,\"microsoft.azurespherev2/catalogs/provisioningpackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs provisioning package\" }\r\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channel\" }\r\n ,\"microsoft.azurespherev2/catalogs/syndicationchannels/deployments\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs syndication channels deployment\" }\r\n ,\"microsoft.azurespherev2/catalogs/updatepackages\": { \"SingularDisplayName\": \"Microsoft.AzureSphereV2 catalogs update package\" }\r\n ,\"microsoft.azurestack/cloudmanifestfiles\": { \"SingularDisplayName\": \"Microsoft.AzureStack cloud manifest file\" }\r\n ,\"microsoft.azurestack/linkedsubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack linked subscription\" }\r\n ,\"microsoft.azurestack/registrations\": { \"SingularDisplayName\": \"Microsoft.AzureStack registration\" }\r\n ,\"microsoft.azurestack/registrations/customersubscriptions\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations customer subscription\" }\r\n ,\"microsoft.azurestack/registrations/products\": { \"SingularDisplayName\": \"Microsoft.AzureStack registrations product\" }\r\n ,\"microsoft.azurestackhci/clusters\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/clusters/updates/updateruns\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/clusters/updatesummaries\": { \"SingularDisplayName\": \"Azure Local\" }\r\n ,\"microsoft.azurestackhci/devicepools\": { \"SingularDisplayName\": \"Azure Stack\" }\r\n ,\"microsoft.azurestackhci/edgedevices\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge device\" }\r\n ,\"microsoft.azurestackhci/edgedevices/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge devices job\" }\r\n ,\"microsoft.azurestackhci/edgemachines\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machine\" }\r\n ,\"microsoft.azurestackhci/edgemachines/jobs\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI edge machines job\" }\r\n ,\"microsoft.azurestackhci/edgenodepools\": { \"SingularDisplayName\": \"Azure Stack\" }\r\n ,\"microsoft.azurestackhci/galleryimages\": { \"SingularDisplayName\": \"Azure Local Gallery image\" }\r\n ,\"microsoft.azurestackhci/logicalnetworks\": { \"SingularDisplayName\": \"Azure Local Logical network\" }\r\n ,\"microsoft.azurestackhci/marketplacegalleryimages\": { \"SingularDisplayName\": \"Azure Local Marketplace Gallery image\" }\r\n ,\"microsoft.azurestackhci/networkinterfaces\": { \"SingularDisplayName\": \"Azure Local VM Network Interface\" }\r\n ,\"microsoft.azurestackhci/networksecuritygroups\": { \"SingularDisplayName\": \"Azure Local Network Security Group\" }\r\n ,\"microsoft.azurestackhci/networksecuritygroups/securityrules\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI network security groups security rule\" }\r\n ,\"microsoft.azurestackhci/storagecontainers\": { \"SingularDisplayName\": \"Azure Local Storage path\" }\r\n ,\"microsoft.azurestackhci/virtualharddisks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual hard disk\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instance\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances guest agent\" }\r\n ,\"microsoft.azurestackhci/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.azurestackhci/virtualmachines\": { \"SingularDisplayName\": \"Azure Local virtual machine - Azure Arc\" }\r\n ,\"microsoft.azurestackhci/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.AzureStackHCI virtual network\" }\r\n ,\"microsoft.backupsolutions/vmwareapplications\": { \"SingularDisplayName\": \"Microsoft.BackupSolutions vmware application\" }\r\n ,\"microsoft.bakeryhybrid/pies\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid py\" }\r\n ,\"microsoft.bakeryhybrid/pies/nestedresourcetype\": { \"SingularDisplayName\": \"Microsoft.BakeryHybrid pies nested resource type\" }\r\n ,\"microsoft.baremetal/baremetalconnections\": { \"SingularDisplayName\": \"Microsoft.BareMetal bare metal connection\" }\r\n ,\"microsoft.baremetal/crayservers\": { \"SingularDisplayName\": \"Cray Server\" }\r\n ,\"microsoft.baremetal/monitoringservers\": { \"SingularDisplayName\": \"Monitoring Server\" }\r\n ,\"microsoft.baremetal/peeringsettings\": { \"SingularDisplayName\": \"Microsoft.BareMetal peering setting\" }\r\n ,\"microsoft.baremetalinfrastructure/baremetalinstances\": { \"SingularDisplayName\": \"BareMetal Instance\" }\r\n ,\"microsoft.baremetalinfrastructure/baremetalstorageinstances\": { \"SingularDisplayName\": \"Microsoft.BareMetalInfrastructure bare metal storage instance\" }\r\n ,\"microsoft.batch/batchaccounts\": { \"SingularDisplayName\": \"Batch account\" }\r\n ,\"microsoft.billing/billingaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing account\" }\r\n ,\"microsoft.billing/billingaccounts/agreements\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts agreement\" }\r\n ,\"microsoft.billing/billingaccounts/associatedtenants\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts associated tenant\" }\r\n ,\"microsoft.billing/billingaccounts/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts available balance\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profile\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/availablebalance\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles available balance\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/customers/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles customers transfer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/instructions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles instruction\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice section\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections product\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles invoice sections transfer\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/paymentmethodlinks\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles payment method link\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles policy\" }\r\n ,\"microsoft.billing/billingaccounts/billingprofiles/transactions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing profiles transaction\" }\r\n ,\"microsoft.billing/billingaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptionaliases\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription aliase\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/billingsubscriptions/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts billing subscriptions invoice\" }\r\n ,\"microsoft.billing/billingaccounts/customers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customer\" }\r\n ,\"microsoft.billing/billingaccounts/customers/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/customers/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers policy\" }\r\n ,\"microsoft.billing/billingaccounts/customers/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts customers product\" }\r\n ,\"microsoft.billing/billingaccounts/departments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts department\" }\r\n ,\"microsoft.billing/billingaccounts/departments/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/departments/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/departments/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts departments enrollment account\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment account\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role assignment\" }\r\n ,\"microsoft.billing/billingaccounts/enrollmentaccounts/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts enrollment accounts billing role definition\" }\r\n ,\"microsoft.billing/billingaccounts/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\r\n ,\"microsoft.billing/billingaccounts/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\r\n ,\"microsoft.billing/billingaccounts/invoices\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice section\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/billingsubscriptions\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections billing subscription\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections product\" }\r\n ,\"microsoft.billing/billingaccounts/invoicesections/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts invoice sections transfer\" }\r\n ,\"microsoft.billing/billingaccounts/lineofcredit\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts line of credit\" }\r\n ,\"microsoft.billing/billingaccounts/migrations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts migration\" }\r\n ,\"microsoft.billing/billingaccounts/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts payment method\" }\r\n ,\"microsoft.billing/billingaccounts/policies\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts policy\" }\r\n ,\"microsoft.billing/billingaccounts/products\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts product\" }\r\n ,\"microsoft.billing/billingaccounts/reservationorders\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation order\" }\r\n ,\"microsoft.billing/billingaccounts/reservationorders/reservations\": { \"SingularDisplayName\": \"Microsoft.Billing billing accounts reservation orders reservation\" }\r\n ,\"microsoft.billing/billingaccounts/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\r\n ,\"microsoft.billing/billingaccounts/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\r\n ,\"microsoft.billing/billingperiods\": { \"SingularDisplayName\": \"Microsoft.Billing billing period\" }\r\n ,\"microsoft.billing/billingproperty\": { \"SingularDisplayName\": \"Microsoft.Billing billing property\" }\r\n ,\"microsoft.billing/billingrequests\": { \"SingularDisplayName\": \"Microsoft.Billing billing request\" }\r\n ,\"microsoft.billing/billingroleassignments\": { \"SingularDisplayName\": \"Microsoft.Billing billing role assignment\" }\r\n ,\"microsoft.billing/billingroledefinitions\": { \"SingularDisplayName\": \"Microsoft.Billing billing role definition\" }\r\n ,\"microsoft.billing/enrollmentaccounts\": { \"SingularDisplayName\": \"Microsoft.Billing enrollment account\" }\r\n ,\"microsoft.billing/paymentmethods\": { \"SingularDisplayName\": \"Microsoft.Billing payment method\" }\r\n ,\"microsoft.billing/policies\": { \"SingularDisplayName\": \"Microsoft.Billing policy\" }\r\n ,\"microsoft.billing/promotions\": { \"SingularDisplayName\": \"Microsoft.Billing promotion\" }\r\n ,\"microsoft.billing/transfers\": { \"SingularDisplayName\": \"Microsoft.Billing transfer\" }\r\n ,\"microsoft.billingbenefits/credits\": { \"SingularDisplayName\": \"Credit\" }\r\n ,\"microsoft.billingbenefits/discounts\": { \"SingularDisplayName\": \"Discount\" }\r\n ,\"microsoft.billingbenefits/incentiveschedules\": { \"SingularDisplayName\": \"Incentive Schedule\" }\r\n ,\"microsoft.billingbenefits/incentiveschedules/milestones\": { \"SingularDisplayName\": \"Milestone\" }\r\n ,\"microsoft.billingbenefits/maccs\": { \"SingularDisplayName\": \"Microsoft Azure Consumption Commitment\" }\r\n ,\"microsoft.billingbenefits/reservationorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits reservation order aliase\" }\r\n ,\"microsoft.billingbenefits/savingsplanorderaliases\": { \"SingularDisplayName\": \"Microsoft.BillingBenefits savings plan order aliase\" }\r\n ,\"microsoft.billingbenefits/savingsplanorders\": { \"SingularDisplayName\": \"Savings plan order\" }\r\n ,\"microsoft.billingbenefits/savingsplanorders/savingsplans\": { \"SingularDisplayName\": \"Savings plan\" }\r\n ,\"microsoft.bing/accounts\": { \"SingularDisplayName\": \"Bing Resource\" }\r\n ,\"microsoft.blockchain/blockchainmembers\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain member\" }\r\n ,\"microsoft.blockchain/blockchainmembers/transactionnodes\": { \"SingularDisplayName\": \"Microsoft.Blockchain blockchain members transaction node\" }\r\n ,\"microsoft.blockchaintokens/tokenservices\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token service\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/blockchainnetworks\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services blockchain network\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/groups\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services group\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/groups/accounts\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services groups account\" }\r\n ,\"microsoft.blockchaintokens/tokenservices/tokentemplates\": { \"SingularDisplayName\": \"Microsoft.BlockchainTokens token services token template\" }\r\n ,\"microsoft.bluefin/instances\": { \"SingularDisplayName\": \"Microsoft.Bluefin instance\" }\r\n ,\"microsoft.bluefin/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances dataset\" }\r\n ,\"microsoft.bluefin/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.Bluefin instances pipeline\" }\r\n ,\"microsoft.blueprint/blueprintassignments\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint assignment\" }\r\n ,\"microsoft.blueprint/blueprints\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprint\" }\r\n ,\"microsoft.blueprint/blueprints/artifacts\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints artifact\" }\r\n ,\"microsoft.blueprint/blueprints/versions\": { \"SingularDisplayName\": \"Microsoft.Blueprint blueprints version\" }\r\n ,\"microsoft.botservice/botservices\": { \"SingularDisplayName\": \"Bot Service\" }\r\n ,\"microsoft.cache/redis\": { \"SingularDisplayName\": \"Redis cache\" }\r\n ,\"microsoft.cache/redisenterprise\": { \"SingularDisplayName\": \"Azure Managed Redis\" }\r\n ,\"microsoft.cache/redisenterprise/databases\": { \"SingularDisplayName\": \"Redis Enterprise database\" }\r\n ,\"microsoft.capacity/reservationorders\": { \"SingularDisplayName\": \"Reservation order\" }\r\n ,\"microsoft.capacity/reservationorders/reservations\": { \"SingularDisplayName\": \"Reservation\" }\r\n ,\"microsoft.cascade/sites\": { \"SingularDisplayName\": \"Microsoft.Cascade site\" }\r\n ,\"microsoft.cdn/cdnwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Content Delivery Network WAF policy\" }\r\n ,\"microsoft.cdn/edgeactions\": { \"SingularDisplayName\": \"Edge Action\" }\r\n ,\"microsoft.cdn/profiles\": { \"SingularDisplayName\": \"Front Door and CDN profile\" }\r\n ,\"microsoft.cdn/profiles/afdendpoints\": { \"SingularDisplayName\": \"Endpoint\" }\r\n ,\"microsoft.cdn/profiles/afdendpoints/routes\": { \"SingularDisplayName\": \"Route\" }\r\n ,\"microsoft.cdn/profiles/customdomains\": { \"SingularDisplayName\": \"Custom domain\" }\r\n ,\"microsoft.cdn/profiles/endpoints\": { \"SingularDisplayName\": \"CDN endpoint\" }\r\n ,\"microsoft.cdn/profiles/endpoints/customdomains\": { \"SingularDisplayName\": \"CDN custom domain\" }\r\n ,\"microsoft.cdn/profiles/endpoints/origins\": { \"SingularDisplayName\": \"CDN origin\" }\r\n ,\"microsoft.cdn/profiles/origingroups\": { \"SingularDisplayName\": \"Origin group\" }\r\n ,\"microsoft.cdn/profiles/origingroups/origins\": { \"SingularDisplayName\": \"Origin\" }\r\n ,\"microsoft.cdn/profiles/rulesets\": { \"SingularDisplayName\": \"Rule set\" }\r\n ,\"microsoft.cdn/profiles/rulesets/rules\": { \"SingularDisplayName\": \"Rule\" }\r\n ,\"microsoft.cdn/profiles/secrets\": { \"SingularDisplayName\": \"Secret\" }\r\n ,\"microsoft.cdn/profiles/securitypolicies\": { \"SingularDisplayName\": \"Security policy\" }\r\n ,\"microsoft.certificateregistration/certificateorders\": { \"SingularDisplayName\": \"App Service certificate\" }\r\n ,\"microsoft.certify/testsuites\": { \"SingularDisplayName\": \"Microsoft.Certify test suite\" }\r\n ,\"microsoft.certify/validationjobs\": { \"SingularDisplayName\": \"Microsoft.Certify validation job\" }\r\n ,\"microsoft.changeanalysis/profile\": { \"SingularDisplayName\": \"Microsoft.ChangeAnalysis profile\" }\r\n ,\"microsoft.changesafety/changestates\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change state\" }\r\n ,\"microsoft.changesafety/changestates/stageprogressions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety change states stage progression\" }\r\n ,\"microsoft.changesafety/stagemaps\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety stage map\" }\r\n ,\"microsoft.changesafety/validations\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validation\" }\r\n ,\"microsoft.changesafety/validators\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validator\" }\r\n ,\"microsoft.changesafety/validators/versions\": { \"SingularDisplayName\": \"Microsoft.ChangeSafety validators version\" }\r\n ,\"microsoft.chaos/experiments\": { \"SingularDisplayName\": \"Chaos Experiment\" }\r\n ,\"microsoft.chaos/privateaccesses\": { \"SingularDisplayName\": \"Agent Private Access\" }\r\n ,\"microsoft.chaos/targets\": { \"SingularDisplayName\": \"Microsoft.Chaos target\" }\r\n ,\"microsoft.chaos/targets/capabilities\": { \"SingularDisplayName\": \"Microsoft.Chaos targets capability\" }\r\n ,\"microsoft.classiccompute/domainnames\": { \"SingularDisplayName\": \"Cloud service (classic)\" }\r\n ,\"microsoft.classiccompute/domainnames/slots/roles\": { \"SingularDisplayName\": \"Cloud service role (classic)\" }\r\n ,\"microsoft.classiccompute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine (classic)\" }\r\n ,\"microsoft.classicnetwork/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group (classic)\" }\r\n ,\"microsoft.classicnetwork/reservedips\": { \"SingularDisplayName\": \"Reserved IP address (classic)\" }\r\n ,\"microsoft.classicnetwork/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network (classic)\" }\r\n })[tolower(id)]\r\n}\r\n", + "$fxv#1": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_2(id: string) {\r\n dynamic({\r\n \"microsoft.classicstorage/storageaccounts\": { \"SingularDisplayName\": \"Storage account (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/disks\": { \"SingularDisplayName\": \"Disk (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/osimages\": { \"SingularDisplayName\": \"OS image (classic)\" }\r\n ,\"microsoft.classicstorage/storageaccounts/vmimages\": { \"SingularDisplayName\": \"VM image (classic)\" }\r\n ,\"microsoft.cleanroom/cleanrooms\": { \"SingularDisplayName\": \"Microsoft.CleanRoom cleanroom\" }\r\n ,\"microsoft.cleanroom/collaborations\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaboration\" }\r\n ,\"microsoft.cleanroom/collaborations/contracts\": { \"SingularDisplayName\": \"Microsoft.CleanRoom collaborations contract\" }\r\n ,\"microsoft.cleanroom/consortiums\": { \"SingularDisplayName\": \"Microsoft.CleanRoom consortium\" }\r\n ,\"microsoft.cleanroom/microservices\": { \"SingularDisplayName\": \"Microsoft.CleanRoom microservice\" }\r\n ,\"microsoft.cloud/hubs\": { \"SingularDisplayName\": \"FinOps hub\" }\r\n ,\"microsoft.clouddeviceplatform/delegatedidentities\": { \"SingularDisplayName\": \"Microsoft.CloudDevicePlatform delegated identity\" }\r\n ,\"microsoft.cloudhealth/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\r\n ,\"microsoft.cloudtest/accounts\": { \"SingularDisplayName\": \"CloudTest Account\" }\r\n ,\"microsoft.cloudtest/buildcaches\": { \"SingularDisplayName\": \"1ES Build Cache\" }\r\n ,\"microsoft.cloudtest/hostedpools\": { \"SingularDisplayName\": \"1ES Hosted Pool\" }\r\n ,\"microsoft.cloudtest/images\": { \"SingularDisplayName\": \"1ES Image\" }\r\n ,\"microsoft.cloudtest/pools\": { \"SingularDisplayName\": \"CloudTest Pool\" }\r\n ,\"microsoft.clusterstor/nodes\": { \"SingularDisplayName\": \"ClusterStor\" }\r\n ,\"microsoft.codesigning/codesigningaccounts\": { \"SingularDisplayName\": \"Trusted Signing Account\" }\r\n ,\"microsoft.codespaces/plans\": { \"SingularDisplayName\": \"Microsoft.Codespaces plan\" }\r\n ,\"microsoft.cognitiveservices/accounts\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.cognitiveservices/accounts/projects\": { \"SingularDisplayName\": \"Azure AI Foundry project\" }\r\n ,\"microsoft.cognitiveservices/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plan\" }\r\n ,\"microsoft.cognitiveservices/commitmentplans/accountassociations\": { \"SingularDisplayName\": \"Microsoft.CognitiveServices commitment plans account association\" }\r\n ,\"microsoft.communication/communicationservices\": { \"SingularDisplayName\": \"Communication Service\" }\r\n ,\"microsoft.communication/emailservices\": { \"SingularDisplayName\": \"Email Communication Service\" }\r\n ,\"microsoft.communication/emailservices/domains\": { \"SingularDisplayName\": \"Email Communication Services Domain\" }\r\n ,\"microsoft.community/communitytrainings\": { \"SingularDisplayName\": \"Community Training\" }\r\n ,\"microsoft.compositesolutions/compositesolutiondefinitions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution definition\" }\r\n ,\"microsoft.compositesolutions/compositesolutions\": { \"SingularDisplayName\": \"Microsoft.CompositeSolutions composite solution\" }\r\n ,\"microsoft.compute/availabilitysets\": { \"SingularDisplayName\": \"Availability set\" }\r\n ,\"microsoft.compute/capacityreservationgroups\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\r\n ,\"microsoft.compute/capacityreservationgroups/capacityreservations\": { \"SingularDisplayName\": \"Capacity reservation\" }\r\n ,\"microsoft.compute/capacityreservationgroupscomputehub\": { \"SingularDisplayName\": \"Capacity Reservation Group\" }\r\n ,\"microsoft.compute/cloudservices\": { \"SingularDisplayName\": \"Cloud service (extended support)\" }\r\n ,\"microsoft.compute/computefleetinstances\": { \"SingularDisplayName\": \"Instance\" }\r\n ,\"microsoft.compute/computefleetscalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.compute/diskaccesses\": { \"SingularDisplayName\": \"Disk Access\" }\r\n ,\"microsoft.compute/diskencryptionsets\": { \"SingularDisplayName\": \"Disk Encryption Set\" }\r\n ,\"microsoft.compute/disks\": { \"SingularDisplayName\": \"Disk\" }\r\n ,\"microsoft.compute/galleries\": { \"SingularDisplayName\": \"Azure compute gallery\" }\r\n ,\"microsoft.compute/galleries/applications\": { \"SingularDisplayName\": \"VM application definition\" }\r\n ,\"microsoft.compute/galleries/applications/versions\": { \"SingularDisplayName\": \"VM application version\" }\r\n ,\"microsoft.compute/galleries/images\": { \"SingularDisplayName\": \"VM image definition\" }\r\n ,\"microsoft.compute/galleries/images/versions\": { \"SingularDisplayName\": \"VM image version\" }\r\n ,\"microsoft.compute/galleries/imagescomputehub\": { \"SingularDisplayName\": \"VM image definition\" }\r\n ,\"microsoft.compute/hostgroups\": { \"SingularDisplayName\": \"Host group\" }\r\n ,\"microsoft.compute/hostgroups/hosts\": { \"SingularDisplayName\": \"Host\" }\r\n ,\"microsoft.compute/hostgroupscomputehub\": { \"SingularDisplayName\": \"Host group\" }\r\n ,\"microsoft.compute/images\": { \"SingularDisplayName\": \"Image\" }\r\n ,\"microsoft.compute/imagescomputehub\": { \"SingularDisplayName\": \"Image\" }\r\n ,\"microsoft.compute/locations/communitygalleries/images\": { \"SingularDisplayName\": \"Community image\" }\r\n ,\"microsoft.compute/locations/communitygalleries/imagescomputehub\": { \"SingularDisplayName\": \"Community image\" }\r\n ,\"microsoft.compute/proximityplacementgroups\": { \"SingularDisplayName\": \"Proximity placement group\" }\r\n ,\"microsoft.compute/proximityplacementgroupscomputehub\": { \"SingularDisplayName\": \"Proximity placement group\" }\r\n ,\"microsoft.compute/restorepointcollections\": { \"SingularDisplayName\": \"Restore Point Collection\" }\r\n ,\"microsoft.compute/restorepointcollections/restorepoints\": { \"SingularDisplayName\": \"Restore Point\" }\r\n ,\"microsoft.compute/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\r\n ,\"microsoft.compute/sshpublickeys\": { \"SingularDisplayName\": \"SSH key\" }\r\n ,\"microsoft.compute/standbypoolinstance\": { \"SingularDisplayName\": \"Standby pool\" }\r\n ,\"microsoft.compute/virtualmachinecomputehub\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.compute/virtualmachineflexinstances\": { \"SingularDisplayName\": \"Instance\" }\r\n ,\"microsoft.compute/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine\" }\r\n ,\"microsoft.compute/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.compute/virtualmachinescalesets\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines\": { \"SingularDisplayName\": \"Virtual machine scale set instance\" }\r\n ,\"microsoft.compute/virtualmachinescalesets/virtualmachines/networkinterfaces/ipconfigurations/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\r\n ,\"microsoft.compute/virtualmachinescalesetscomputehub\": { \"SingularDisplayName\": \"Virtual machine scale set\" }\r\n ,\"microsoft.computehub/advisorcost\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisoroperationalexcellence\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorperformance\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorreliability\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/advisorsecurity\": { \"SingularDisplayName\": \"Recommendations\" }\r\n ,\"microsoft.computehub/all\": { \"SingularDisplayName\": \"All resources\" }\r\n ,\"microsoft.computehub/backup\": { \"SingularDisplayName\": \"Backup job\" }\r\n ,\"microsoft.computehub/computehubmain\": { \"SingularDisplayName\": \"Compute infrastructure\" }\r\n ,\"microsoft.computehub/healthevents\": { \"SingularDisplayName\": \"Health events\" }\r\n ,\"microsoft.computehub/linuxostype\": { \"SingularDisplayName\": \"Linux OS\" }\r\n ,\"microsoft.computehub/microsoftdefenderfreetrialsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\r\n ,\"microsoft.computehub/microsoftdefenderstandardsubscription\": { \"SingularDisplayName\": \"Microsoft defender\" }\r\n ,\"microsoft.computehub/outages\": { \"SingularDisplayName\": \"Outages\" }\r\n ,\"microsoft.computehub/powerstatedeallocated\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/powerstaterunning\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/powerstatestopped\": { \"SingularDisplayName\": \"Power states\" }\r\n ,\"microsoft.computehub/provisioningstatefailedresources\": { \"SingularDisplayName\": \"Provisioning states\" }\r\n ,\"microsoft.computehub/provisioningstatesucceededresources\": { \"SingularDisplayName\": \"Provisioning states\" }\r\n ,\"microsoft.computehub/windowsostype\": { \"SingularDisplayName\": \"Windows OS\" }\r\n ,\"microsoft.computeschedule/autoactions\": { \"SingularDisplayName\": \"Automatic Action\" }\r\n ,\"microsoft.computeschedule/autoactions/occurrences\": { \"SingularDisplayName\": \"Microsoft.ComputeSchedule auto actions occurrence\" }\r\n ,\"microsoft.confidentialledger/ledgers\": { \"SingularDisplayName\": \"Confidential Ledger\" }\r\n ,\"microsoft.confidentialledger/managedccfs\": { \"SingularDisplayName\": \"Managed CCF App\" }\r\n ,\"microsoft.confluent/agreements\": { \"SingularDisplayName\": \"Microsoft.Confluent agreement\" }\r\n ,\"microsoft.confluent/organizations\": { \"SingularDisplayName\": \"Confluent organization\" }\r\n ,\"microsoft.connectedcache/cachenodes\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\r\n ,\"microsoft.connectedcache/enterprisecustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\r\n ,\"microsoft.connectedcache/enterprisemcccustomers\": { \"SingularDisplayName\": \"Connected Cache for Enterprise & Education\" }\r\n ,\"microsoft.connectedcache/enterprisemcccustomers/enterprisemcccachenodes\": { \"SingularDisplayName\": \"MCC CacheNode for Enterprise\" }\r\n ,\"microsoft.connectedcache/ispcustomers\": { \"SingularDisplayName\": \"Connected Cache for ISP\" }\r\n ,\"microsoft.connectedcredentials/credentials\": { \"SingularDisplayName\": \"Microsoft.ConnectedCredentials credential\" }\r\n ,\"microsoft.connectedvehicle/platformaccounts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVehicle platform account\" }\r\n ,\"microsoft.connectedvmwarevsphere/clusters\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere cluster\" }\r\n ,\"microsoft.connectedvmwarevsphere/datastores\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere datastore\" }\r\n ,\"microsoft.connectedvmwarevsphere/hosts\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere host\" }\r\n ,\"microsoft.connectedvmwarevsphere/resourcepools\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere resource pool\" }\r\n ,\"microsoft.connectedvmwarevsphere/vcenters\": { \"SingularDisplayName\": \"VMware vCenter\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instance\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances guest agent\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachines\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual machine template\" }\r\n ,\"microsoft.connectedvmwarevsphere/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ConnectedVMwarevSphere virtual network\" }\r\n ,\"microsoft.consumption/budgets\": { \"SingularDisplayName\": \"Microsoft.Consumption budget\" }\r\n ,\"microsoft.consumption/credits\": { \"SingularDisplayName\": \"Microsoft.Consumption credit\" }\r\n ,\"microsoft.consumption/pricesheets\": { \"SingularDisplayName\": \"Microsoft.Consumption pricesheet\" }\r\n ,\"microsoft.containerinstance/containergroupprofiles\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profile\" }\r\n ,\"microsoft.containerinstance/containergroupprofiles/revisions\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance container group profiles revision\" }\r\n ,\"microsoft.containerinstance/containergroups\": { \"SingularDisplayName\": \"Container instances\" }\r\n ,\"microsoft.containerinstance/ngroups\": { \"SingularDisplayName\": \"Microsoft.ContainerInstance ngroup\" }\r\n ,\"microsoft.containerregistry/registries\": { \"SingularDisplayName\": \"Container registry\" }\r\n ,\"microsoft.containerregistry/registries/replications\": { \"SingularDisplayName\": \"Container registry replication\" }\r\n ,\"microsoft.containerregistry/registries/scopemaps\": { \"SingularDisplayName\": \"Container registry scope map\" }\r\n ,\"microsoft.containerregistry/registries/tokens\": { \"SingularDisplayName\": \"Container registry token\" }\r\n ,\"microsoft.containerregistry/registries/webhooks\": { \"SingularDisplayName\": \"Container registry webhook\" }\r\n ,\"microsoft.containerservice/fleets\": { \"SingularDisplayName\": \"Kubernetes fleet manager\" }\r\n ,\"microsoft.containerservice/managedclusters\": { \"SingularDisplayName\": \"Kubernetes service\" }\r\n ,\"microsoft.containerservice/managedclusters/managednamespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\r\n ,\"microsoft.containerservice/managedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes namespace\" }\r\n ,\"microsoft.containerservice/managedclusters/namespaces\": { \"SingularDisplayName\": \"Managed namespace\" }\r\n ,\"microsoft.containerservice/managedclustersnapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService managedclustersnapshot\" }\r\n ,\"microsoft.containerservice/snapshots\": { \"SingularDisplayName\": \"Microsoft.ContainerService snapshot\" }\r\n ,\"microsoft.containerstorage/pools\": { \"SingularDisplayName\": \"Container storage\" }\r\n ,\"microsoft.costmanagement/alerts\": { \"SingularDisplayName\": \"Microsoft.CostManagement alert\" }\r\n ,\"microsoft.costmanagement/budgets\": { \"SingularDisplayName\": \"Microsoft.CostManagement budget\" }\r\n ,\"microsoft.costmanagement/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement cloud connector\" }\r\n ,\"microsoft.costmanagement/connectors\": { \"SingularDisplayName\": \"Microsoft.CostManagement connector\" }\r\n ,\"microsoft.costmanagement/costallocationrules\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost allocation rule\" }\r\n ,\"microsoft.costmanagement/costdetailsoperationresults\": { \"SingularDisplayName\": \"Microsoft.CostManagement cost details operation result\" }\r\n ,\"microsoft.costmanagement/exports\": { \"SingularDisplayName\": \"Microsoft.CostManagement export\" }\r\n ,\"microsoft.costmanagement/externalbillingaccounts\": { \"SingularDisplayName\": \"Microsoft.CostManagement external billing account\" }\r\n ,\"microsoft.costmanagement/externalsubscriptions\": { \"SingularDisplayName\": \"Microsoft.CostManagement external subscription\" }\r\n ,\"microsoft.costmanagement/markuprules\": { \"SingularDisplayName\": \"Microsoft.CostManagement markup rule\" }\r\n ,\"microsoft.costmanagement/operationstatus\": { \"SingularDisplayName\": \"Microsoft.CostManagement operation statu\" }\r\n ,\"microsoft.costmanagement/reportconfigs\": { \"SingularDisplayName\": \"Microsoft.CostManagement reportconfig\" }\r\n ,\"microsoft.costmanagement/reports\": { \"SingularDisplayName\": \"Microsoft.CostManagement report\" }\r\n ,\"microsoft.costmanagement/scheduledactions\": { \"SingularDisplayName\": \"Microsoft.CostManagement scheduled action\" }\r\n ,\"microsoft.costmanagement/settings\": { \"SingularDisplayName\": \"Microsoft.CostManagement setting\" }\r\n ,\"microsoft.costmanagement/views\": { \"SingularDisplayName\": \"Microsoft.CostManagement view\" }\r\n ,\"microsoft.customerlockbox/requests\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox request\" }\r\n ,\"microsoft.customerlockbox/tenantoptedin\": { \"SingularDisplayName\": \"Microsoft.CustomerLockbox tenant opted in\" }\r\n ,\"microsoft.customproviders/associations\": { \"SingularDisplayName\": \"Microsoft.CustomProviders association\" }\r\n ,\"microsoft.customproviders/resourceproviders\": { \"SingularDisplayName\": \"Microsoft.CustomProviders resource provider\" }\r\n ,\"microsoft.dashboard/dashboards\": { \"SingularDisplayName\": \"Azure Monitor dashboards with Grafana\" }\r\n ,\"microsoft.dashboard/grafana\": { \"SingularDisplayName\": \"Azure Managed Grafana\" }\r\n ,\"microsoft.dataaccelerator/indexclusters\": { \"SingularDisplayName\": \"Microsoft.DataAccelerator index cluster\" }\r\n ,\"microsoft.databasefleetmanager/fleets\": { \"SingularDisplayName\": \"Database fleet manager\" }\r\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces\": { \"SingularDisplayName\": \"Fleetspaces\" }\r\n ,\"microsoft.databasefleetmanager/fleets/fleetspaces/databases\": { \"SingularDisplayName\": \"Fleet managed database\" }\r\n ,\"microsoft.databasefleetmanager/fleets/tiers\": { \"SingularDisplayName\": \"tier\" }\r\n ,\"microsoft.databasewatcher/watchers\": { \"SingularDisplayName\": \"Database watcher\" }\r\n ,\"microsoft.databox/jobs\": { \"SingularDisplayName\": \"Azure Data Box\" }\r\n ,\"microsoft.databoxedge/databoxedgedevices\": { \"SingularDisplayName\": \"Azure Stack Edge / Data Box Gateway\" }\r\n ,\"microsoft.databricks/accessconnectors\": { \"SingularDisplayName\": \"Access Connector for Azure Databricks\" }\r\n ,\"microsoft.databricks/workspaces\": { \"SingularDisplayName\": \"Azure Databricks Service\" }\r\n ,\"microsoft.datacatalog/catalogs\": { \"SingularDisplayName\": \"Data catalog\" }\r\n ,\"microsoft.datacollaboration/workspaces\": { \"SingularDisplayName\": \"Project CI\" }\r\n ,\"microsoft.datadog/agreements\": { \"SingularDisplayName\": \"Microsoft.Datadog agreement\" }\r\n ,\"microsoft.datadog/monitors\": { \"SingularDisplayName\": \"Datadog\" }\r\n ,\"microsoft.datadog/subscriptionstatuses\": { \"SingularDisplayName\": \"Microsoft.Datadog subscription statuse\" }\r\n ,\"microsoft.datafactory/datafactories\": { \"SingularDisplayName\": \"Data factory\" }\r\n ,\"microsoft.datafactory/factories\": { \"SingularDisplayName\": \"Data factory (V2)\" }\r\n ,\"microsoft.datafactory/factories/pipelines\": { \"SingularDisplayName\": \"Data Factory pipeline\" }\r\n ,\"microsoft.datafactory/factories/triggers\": { \"SingularDisplayName\": \"Data Factory trigger\" }\r\n ,\"microsoft.datalakeanalytics/accounts\": { \"SingularDisplayName\": \"Data Lake Analytics account\" }\r\n ,\"microsoft.datalakestore/accounts\": { \"SingularDisplayName\": \"Data Lake Storage Gen1\" }\r\n ,\"microsoft.datamigration/databasemigrations\": { \"SingularDisplayName\": \"Microsoft.DataMigration database migration\" }\r\n ,\"microsoft.datamigration/migrationservices\": { \"SingularDisplayName\": \"Microsoft.DataMigration migration service\" }\r\n ,\"microsoft.datamigration/services\": { \"SingularDisplayName\": \"Azure Database Migration Service (classic)\" }\r\n ,\"microsoft.datamigration/services/projects\": { \"SingularDisplayName\": \"Azure Database Migration Project\" }\r\n ,\"microsoft.datamigration/sqlmigrationservices\": { \"SingularDisplayName\": \"Azure Database Migration Service\" }\r\n ,\"microsoft.dataprotection/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\r\n ,\"microsoft.dataprotection/resourceguards\": { \"SingularDisplayName\": \"Resource Guard\" }\r\n ,\"microsoft.datareplication/replicationfabrics\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabric\" }\r\n ,\"microsoft.datareplication/replicationfabrics/fabricagents\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agent\" }\r\n ,\"microsoft.datareplication/replicationfabrics/fabricagents/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics fabric agents operation\" }\r\n ,\"microsoft.datareplication/replicationfabrics/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication fabrics operation\" }\r\n ,\"microsoft.datareplication/replicationvaults\": { \"SingularDisplayName\": \"Data replication vault\" }\r\n ,\"microsoft.datareplication/replicationvaults/alertsettings\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults alert setting\" }\r\n ,\"microsoft.datareplication/replicationvaults/events\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults event\" }\r\n ,\"microsoft.datareplication/replicationvaults/jobs\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults job\" }\r\n ,\"microsoft.datareplication/replicationvaults/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults jobs operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnectionproxies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection proxy\" }\r\n ,\"microsoft.datareplication/replicationvaults/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private endpoint connection\" }\r\n ,\"microsoft.datareplication/replicationvaults/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults private link resource\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected item\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/protecteditems/recoverypoints\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults protected items recovery point\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationextensions\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extension\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationextensions/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication extensions operation\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policy\" }\r\n ,\"microsoft.datareplication/replicationvaults/replicationpolicies/operations\": { \"SingularDisplayName\": \"Microsoft.DataReplication replication vaults replication policies operation\" }\r\n ,\"microsoft.datashare/accounts\": { \"SingularDisplayName\": \"Data Share\" }\r\n ,\"microsoft.dbformariadb/servers\": { \"SingularDisplayName\": \"Azure Database for MariaDB server\" }\r\n ,\"microsoft.dbformysql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for MySQL flexible server\" }\r\n ,\"microsoft.dbformysql/servers\": { \"SingularDisplayName\": \"MySQL server\" }\r\n ,\"microsoft.dbforpostgresql/flexibleservers\": { \"SingularDisplayName\": \"Azure Database for PostgreSQL flexible server\" }\r\n ,\"microsoft.dbforpostgresql/servergroupsv2\": { \"SingularDisplayName\": \"Azure Cosmos DB for PostgreSQL Cluster\" }\r\n ,\"microsoft.dbforpostgresql/servers\": { \"SingularDisplayName\": \"PostgreSQL server\" }\r\n ,\"microsoft.delegatednetwork/controller\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork controller\" }\r\n ,\"microsoft.delegatednetwork/delegatedsubnets\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork delegated subnet\" }\r\n ,\"microsoft.delegatednetwork/orchestrators\": { \"SingularDisplayName\": \"Microsoft.DelegatedNetwork orchestrator\" }\r\n ,\"microsoft.dependencymap/maps\": { \"SingularDisplayName\": \"Microsoft.DependencyMap map\" }\r\n ,\"microsoft.dependencymap/maps/discoverysources\": { \"SingularDisplayName\": \"Microsoft.DependencyMap maps discovery source\" }\r\n ,\"microsoft.deploymentmanager/artifactsources\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager artifact source\" }\r\n ,\"microsoft.deploymentmanager/rollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topology\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies/services\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies service\" }\r\n ,\"microsoft.deploymentmanager/servicetopologies/services/serviceunits\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager service topologies services service unit\" }\r\n ,\"microsoft.deploymentmanager/steps\": { \"SingularDisplayName\": \"Microsoft.DeploymentManager step\" }\r\n ,\"microsoft.desktopvirtualization/appattachpackages\": { \"SingularDisplayName\": \"App attach package\" }\r\n ,\"microsoft.desktopvirtualization/applicationgroups\": { \"SingularDisplayName\": \"Application group\" }\r\n ,\"microsoft.desktopvirtualization/hostpools\": { \"SingularDisplayName\": \"Host pool\" }\r\n ,\"microsoft.desktopvirtualization/scalingplans\": { \"SingularDisplayName\": \"Scaling plan\" }\r\n ,\"microsoft.desktopvirtualization/workspaces\": { \"SingularDisplayName\": \"Workspace\" }\r\n ,\"microsoft.devai/instances\": { \"SingularDisplayName\": \"Microsoft.DevAI instance\" }\r\n ,\"microsoft.devai/instances/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances experiment\" }\r\n ,\"microsoft.devai/instances/sandboxes\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandbox\" }\r\n ,\"microsoft.devai/instances/sandboxes/experiments\": { \"SingularDisplayName\": \"Microsoft.DevAI instances sandboxes experiment\" }\r\n ,\"microsoft.devcenter/devcenters\": { \"SingularDisplayName\": \"Dev center\" }\r\n ,\"microsoft.devcenter/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Dev Box definition\" }\r\n ,\"microsoft.devcenter/networkconnections\": { \"SingularDisplayName\": \"Network connection\" }\r\n ,\"microsoft.devcenter/plans\": { \"SingularDisplayName\": \"Dev center plan\" }\r\n ,\"microsoft.devcenter/projects\": { \"SingularDisplayName\": \"Project\" }\r\n ,\"microsoft.devcenter/projects/pools\": { \"SingularDisplayName\": \"Pool\" }\r\n ,\"microsoft.developmentwindows365/developmentcloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.DevelopmentWindows365 development cloud pc delegated msi\" }\r\n ,\"microsoft.devhub/iacprofiles\": { \"SingularDisplayName\": \"Infrastructure as Code Automation\" }\r\n ,\"microsoft.devhub/templates\": { \"SingularDisplayName\": \"Microsoft.DevHub template\" }\r\n ,\"microsoft.devhub/templates/versions\": { \"SingularDisplayName\": \"Microsoft.DevHub templates version\" }\r\n ,\"microsoft.devhub/workflows\": { \"SingularDisplayName\": \"Microsoft.DevHub workflow\" }\r\n ,\"microsoft.deviceonboarding/discoveryservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery service\" }\r\n ,\"microsoft.deviceonboarding/discoveryservices/ownershipvoucherpublickeys\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding discovery services ownership voucher public key\" }\r\n ,\"microsoft.deviceonboarding/onboardingservices\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding service\" }\r\n ,\"microsoft.deviceonboarding/onboardingservices/policies\": { \"SingularDisplayName\": \"Microsoft.DeviceOnboarding onboarding services policy\" }\r\n ,\"microsoft.deviceregistry/assetendpointprofiles\": { \"SingularDisplayName\": \"IoT Asset Endpoint Profile\" }\r\n ,\"microsoft.deviceregistry/assets\": { \"SingularDisplayName\": \"IoT Asset\" }\r\n ,\"microsoft.deviceregistry/billingcontainers\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry billing container\" }\r\n ,\"microsoft.deviceregistry/devices\": { \"SingularDisplayName\": \"IoT Device\" }\r\n ,\"microsoft.deviceregistry/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry discovered asset\" }\r\n ,\"microsoft.deviceregistry/namespaces\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespace\" }\r\n ,\"microsoft.deviceregistry/namespaces/assetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/namespaces/assets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces asset\" }\r\n ,\"microsoft.deviceregistry/namespaces/devices\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces device\" }\r\n ,\"microsoft.deviceregistry/namespaces/discoveredassetendpointprofiles\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset endpoint profile\" }\r\n ,\"microsoft.deviceregistry/namespaces/discoveredassets\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry namespaces discovered asset\" }\r\n ,\"microsoft.deviceregistry/schemaregistries\": { \"SingularDisplayName\": \"IoT Schema Registry\" }\r\n ,\"microsoft.deviceregistry/schemaregistries/schemas\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schema\" }\r\n ,\"microsoft.deviceregistry/schemaregistries/schemas/schemaversions\": { \"SingularDisplayName\": \"Microsoft.DeviceRegistry schema registries schemas schema version\" }\r\n ,\"microsoft.devices/iothubs\": { \"SingularDisplayName\": \"IoT hub\" }\r\n ,\"microsoft.devices/provisioningservices\": { \"SingularDisplayName\": \"Azure IoT Hub Device Provisioning Service (DPS)\" }\r\n ,\"microsoft.deviceupdate/accounts\": { \"SingularDisplayName\": \"Device Update for IoT Hub\" }\r\n ,\"microsoft.deviceupdate/updateaccounts\": { \"SingularDisplayName\": \"Device Update Account\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/activedeployments\": { \"SingularDisplayName\": \"Device Update Active Deployment\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/agents\": { \"SingularDisplayName\": \"Device Update Agent\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/deployments\": { \"SingularDisplayName\": \"Device Update Deployment\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/deviceclasses\": { \"SingularDisplayName\": \"Device Update Device Class\" }\r\n ,\"microsoft.deviceupdate/updateaccounts/updates\": { \"SingularDisplayName\": \"Device Update\" }\r\n ,\"microsoft.devops/pipelines\": { \"SingularDisplayName\": \"Microsoft.DevOps pipeline\" }\r\n ,\"microsoft.devopsinfrastructure/pools\": { \"SingularDisplayName\": \"Managed DevOps Pool\" }\r\n ,\"microsoft.devspaces/controllers\": { \"SingularDisplayName\": \"Microsoft.DevSpaces controller\" }\r\n ,\"microsoft.devtestlab/labs\": { \"SingularDisplayName\": \"DevTest lab\" }\r\n ,\"microsoft.devtestlab/labs/virtualmachines\": { \"SingularDisplayName\": \"DevTest Lab virtual machine\" }\r\n ,\"microsoft.devtestlab/schedules\": { \"SingularDisplayName\": \"Microsoft.DevTestLab schedule\" }\r\n ,\"microsoft.devtunnels/tunnelplans\": { \"SingularDisplayName\": \"Dev Tunnels Domain\" }\r\n ,\"microsoft.diagnostics/apollo\": { \"SingularDisplayName\": \"Microsoft.Diagnostics apollo\" }\r\n ,\"microsoft.digitaltwins/digitaltwinsinstances\": { \"SingularDisplayName\": \"Azure Digital Twins\" }\r\n ,\"microsoft.discovery/agents\": { \"SingularDisplayName\": \"Microsoft Discovery Agent\" }\r\n ,\"microsoft.discovery/bookshelves\": { \"SingularDisplayName\": \"Microsoft Discovery Bookshelf\" }\r\n ,\"microsoft.discovery/datacontainers\": { \"SingularDisplayName\": \"Microsoft Discovery Data Container\" }\r\n ,\"microsoft.discovery/datacontainers/dataassets\": { \"SingularDisplayName\": \"Data asset\" }\r\n ,\"microsoft.discovery/models\": { \"SingularDisplayName\": \"Microsoft Discovery Model\" }\r\n ,\"microsoft.discovery/storages\": { \"SingularDisplayName\": \"Microsoft Discovery Storage\" }\r\n ,\"microsoft.discovery/supercomputers\": { \"SingularDisplayName\": \"Microsoft Discovery Supercomputer\" }\r\n ,\"microsoft.discovery/supercomputers/nodepools\": { \"SingularDisplayName\": \"Nodepool\" }\r\n ,\"microsoft.discovery/tools\": { \"SingularDisplayName\": \"Microsoft Discovery Tool\" }\r\n ,\"microsoft.discovery/workflows\": { \"SingularDisplayName\": \"Microsoft Discovery Workflow\" }\r\n ,\"microsoft.discovery/workspaces\": { \"SingularDisplayName\": \"Microsoft Discovery Workspace\" }\r\n ,\"microsoft.discovery/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft Discovery Project\" }\r\n ,\"microsoft.documentdb/cassandraclusters\": { \"SingularDisplayName\": \"Azure Managed Instance for Apache Cassandra\" }\r\n ,\"microsoft.documentdb/databaseaccounts\": { \"SingularDisplayName\": \"Cosmos DB account\" }\r\n ,\"microsoft.documentdb/fleets\": { \"SingularDisplayName\": \"Azure Cosmos DB Fleet\" }\r\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccounts\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\r\n ,\"microsoft.documentdb/fleetspacepotentialdatabaseaccountswithlocations\": { \"SingularDisplayName\": \"Potential Azure Cosmos DB account\" }\r\n ,\"microsoft.documentdb/mongoclusters\": { \"SingularDisplayName\": \"Azure Cosmos DB for MongoDB (vCore)\" }\r\n ,\"microsoft.documentdb/throughputpools\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pool\" }\r\n ,\"microsoft.documentdb/throughputpools/throughputpoolaccounts\": { \"SingularDisplayName\": \"Microsoft.DocumentDB throughput pools throughput pool account\" }\r\n ,\"microsoft.domainregistration/domains\": { \"SingularDisplayName\": \"App Service Domain\" }\r\n ,\"microsoft.domainregistration/topleveldomains\": { \"SingularDisplayName\": \"Microsoft.DomainRegistration top level domain\" }\r\n ,\"microsoft.durabletask/namespaces\": { \"SingularDisplayName\": \"Microsoft.DurableTask namespace\" }\r\n ,\"microsoft.durabletask/namespaces/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\r\n ,\"microsoft.durabletask/schedulers\": { \"SingularDisplayName\": \"Durable Task Scheduler\" }\r\n ,\"microsoft.durabletask/schedulers/taskhubs\": { \"SingularDisplayName\": \"Task Hub\" }\r\n ,\"microsoft.dynamics365fraudprotection/instances\": { \"SingularDisplayName\": \"Microsoft.Dynamics365FraudProtection instance\" }\r\n ,\"microsoft.easm/workspaces\": { \"SingularDisplayName\": \"Microsoft Defender EASM\" }\r\n ,\"microsoft.edge/configurations\": { \"SingularDisplayName\": \"Site configuration\" }\r\n ,\"microsoft.edge/configurations/arcgatewayconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations arc gateway configuration\" }\r\n ,\"microsoft.edge/configurations/connectivityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations connectivity configuration\" }\r\n ,\"microsoft.edge/configurations/dynamicconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configuration\" }\r\n ,\"microsoft.edge/configurations/dynamicconfigurations/versions\": { \"SingularDisplayName\": \"Microsoft.Edge configurations dynamic configurations version\" }\r\n ,\"microsoft.edge/configurations/networkconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations network configuration\" }\r\n ,\"microsoft.edge/configurations/securityconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations security configuration\" }\r\n ,\"microsoft.edge/configurations/timeserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Edge configurations time server configuration\" }\r\n ,\"microsoft.edge/connectivitystatuses\": { \"SingularDisplayName\": \"Microsoft.Edge connectivity statuse\" }\r\n ,\"microsoft.edge/disconnectedoperations\": { \"SingularDisplayName\": \"Azure Local - disconnected operations\" }\r\n ,\"microsoft.edge/siteawareresourcetypes\": { \"SingularDisplayName\": \"Microsoft.Edge site aware resource type\" }\r\n ,\"microsoft.edge/sites\": { \"SingularDisplayName\": \"Site manager - Azure Arc\" }\r\n ,\"microsoft.edge/updates\": { \"SingularDisplayName\": \"Microsoft.Edge update\" }\r\n ,\"microsoft.edgemarketplace/offers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace offer\" }\r\n ,\"microsoft.edgemarketplace/publishers\": { \"SingularDisplayName\": \"Microsoft.EdgeMarketplace publisher\" }\r\n ,\"microsoft.edgeorder/addresses\": { \"SingularDisplayName\": \"Azure Edge Hardware Center Address\" }\r\n ,\"microsoft.edgeorder/bootstrapconfigurations\": { \"SingularDisplayName\": \"Site Key\" }\r\n ,\"microsoft.edgeorder/orderitems\": { \"SingularDisplayName\": \"Azure Edge Hardware Center\" }\r\n ,\"microsoft.edgeorder/virtual_orderitems\": { \"SingularDisplayName\": \"Device\" }\r\n ,\"microsoft.edgezones/extendedzones\": { \"SingularDisplayName\": \"Microsoft.EdgeZones extended zone\" }\r\n ,\"microsoft.education/grants\": { \"SingularDisplayName\": \"Microsoft.Education grant\" }\r\n ,\"microsoft.education/labs\": { \"SingularDisplayName\": \"Microsoft.Education lab\" }\r\n ,\"microsoft.education/labs/joinrequests\": { \"SingularDisplayName\": \"Microsoft.Education labs join request\" }\r\n ,\"microsoft.education/labs/students\": { \"SingularDisplayName\": \"Microsoft.Education labs student\" }\r\n ,\"microsoft.education/studentlabs\": { \"SingularDisplayName\": \"Microsoft.Education student lab\" }\r\n ,\"microsoft.elastic/monitors\": { \"SingularDisplayName\": \"Elastic Cloud Resource\" }\r\n ,\"microsoft.elasticsan/elasticsans\": { \"SingularDisplayName\": \"Elastic SAN\" }\r\n ,\"microsoft.energydataplatform/energyservices\": { \"SingularDisplayName\": \"Microsoft.EnergyDataPlatform energy service\" }\r\n ,\"microsoft.enterpriseknowledgegraph/services\": { \"SingularDisplayName\": \"Microsoft.EnterpriseKnowledgeGraph service\" }\r\n ,\"microsoft.enterprisesupport/enterprisesupports\": { \"SingularDisplayName\": \"Microsoft.EnterpriseSupport enterprise support\" }\r\n ,\"microsoft.eventgrid/domains\": { \"SingularDisplayName\": \"Event Grid Domain\" }\r\n ,\"microsoft.eventgrid/domains/topics\": { \"SingularDisplayName\": \"Event Grid Domain Topic\" }\r\n ,\"microsoft.eventgrid/eventsubscriptions\": { \"SingularDisplayName\": \"Microsoft.EventGrid event subscription\" }\r\n ,\"microsoft.eventgrid/extensiontopics\": { \"SingularDisplayName\": \"Event Grid extension topic\" }\r\n ,\"microsoft.eventgrid/namespaces\": { \"SingularDisplayName\": \"Event Grid Namespace\" }\r\n ,\"microsoft.eventgrid/namespaces/topics\": { \"SingularDisplayName\": \"Event Grid Namespace Topic\" }\r\n ,\"microsoft.eventgrid/namespaces/topics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Subscription\" }\r\n ,\"microsoft.eventgrid/namespaces/topicspaces\": { \"SingularDisplayName\": \"Event Grid Topic Space\" }\r\n ,\"microsoft.eventgrid/partnerconfigurations\": { \"SingularDisplayName\": \"Event Grid Partner Configuration\" }\r\n ,\"microsoft.eventgrid/partnerdestinations\": { \"SingularDisplayName\": \"Event Grid Partner Destination\" }\r\n ,\"microsoft.eventgrid/partnernamespaces\": { \"SingularDisplayName\": \"Event Grid Partner Namespace\" }\r\n ,\"microsoft.eventgrid/partnernamespaces/channels\": { \"SingularDisplayName\": \"Event Grid Channel\" }\r\n ,\"microsoft.eventgrid/partnerregistrations\": { \"SingularDisplayName\": \"Event Grid Partner Registration\" }\r\n ,\"microsoft.eventgrid/partnertopics\": { \"SingularDisplayName\": \"Event Grid Partner Topic\" }\r\n ,\"microsoft.eventgrid/systemtopics\": { \"SingularDisplayName\": \"Event Grid System Topic\" }\r\n ,\"microsoft.eventgrid/systemtopics/eventsubscriptions\": { \"SingularDisplayName\": \"Event Grid Subscriptions\" }\r\n ,\"microsoft.eventgrid/topics\": { \"SingularDisplayName\": \"Event Grid Topic\" }\r\n ,\"microsoft.eventgrid/topictypes\": { \"SingularDisplayName\": \"Microsoft.EventGrid topic type\" }\r\n ,\"microsoft.eventgrid/verifiedpartners\": { \"SingularDisplayName\": \"Microsoft.EventGrid verified partner\" }\r\n ,\"microsoft.eventhub/clusters\": { \"SingularDisplayName\": \"Event Hubs Cluster\" }\r\n ,\"microsoft.eventhub/namespaces\": { \"SingularDisplayName\": \"Event Hubs namespace\" }\r\n ,\"microsoft.eventhub/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Event Hubs Geo-DR Alias\" }\r\n ,\"microsoft.eventhub/namespaces/eventhubs\": { \"SingularDisplayName\": \"Event Hubs Instance\" }\r\n ,\"microsoft.eventhub/namespaces/providers/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\r\n ,\"microsoft.eventhub/namespaces/schemagroups\": { \"SingularDisplayName\": \"Schema Group\" }\r\n ,\"microsoft.experimentation/experimentworkspaces\": { \"SingularDisplayName\": \"Experiment Workspace\" }\r\n ,\"microsoft.extendedlocation/customlocations\": { \"SingularDisplayName\": \"Custom location\" }\r\n ,\"microsoft.fabric/capacities\": { \"SingularDisplayName\": \"Fabric Capacity\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/operationresults\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric operation result\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private endpoint connection\" }\r\n ,\"microsoft.fabric/privatelinkservicesforfabric/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Fabric private link services for fabric private link resource\" }\r\n ,\"microsoft.fairfieldgardens/deviceprovisioningstates\": { \"SingularDisplayName\": \"Microsoft.FairfieldGardens device provisioning state\" }\r\n ,\"microsoft.fairfieldgardens/provisioningresources\": { \"SingularDisplayName\": \"Fairfield Gardens\" }\r\n ,\"microsoft.fairfieldgardens/provisioningresources/provisioningpolicies\": { \"SingularDisplayName\": \"Provisioning policy\" }\r\n ,\"microsoft.falcon/namespaces\": { \"SingularDisplayName\": \"Microsoft.Falcon namespace\" }\r\n ,\"microsoft.features/featureprovidernamespaces/featureconfigurations\": { \"SingularDisplayName\": \"Preview features\" }\r\n ,\"microsoft.fidalgo/devcenters\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenter\" }\r\n ,\"microsoft.fidalgo/devcenters/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters attachednetwork\" }\r\n ,\"microsoft.fidalgo/devcenters/catalogs\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalog\" }\r\n ,\"microsoft.fidalgo/devcenters/catalogs/items\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters catalogs item\" }\r\n ,\"microsoft.fidalgo/devcenters/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters devboxdefinition\" }\r\n ,\"microsoft.fidalgo/devcenters/environmenttypes\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters environment type\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters gallery\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries/images\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries image\" }\r\n ,\"microsoft.fidalgo/devcenters/galleries/images/versions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters galleries images version\" }\r\n ,\"microsoft.fidalgo/devcenters/mappings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo devcenters mapping\" }\r\n ,\"microsoft.fidalgo/machinedefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo machinedefinition\" }\r\n ,\"microsoft.fidalgo/networksettings\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksetting\" }\r\n ,\"microsoft.fidalgo/networksettings/healthchecks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo networksettings healthcheck\" }\r\n ,\"microsoft.fidalgo/projects\": { \"SingularDisplayName\": \"Microsoft.Fidalgo project\" }\r\n ,\"microsoft.fidalgo/projects/attachednetworks\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects attachednetwork\" }\r\n ,\"microsoft.fidalgo/projects/devboxdefinitions\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects devboxdefinition\" }\r\n ,\"microsoft.fidalgo/projects/environments\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects environment\" }\r\n ,\"microsoft.fidalgo/projects/pools\": { \"SingularDisplayName\": \"Microsoft.Fidalgo projects pool\" }\r\n ,\"microsoft.fileshares/fileshares\": { \"SingularDisplayName\": \"File share\" }\r\n ,\"microsoft.fluidrelay/fluidrelayservers\": { \"SingularDisplayName\": \"Fluid Relay\" }\r\n ,\"microsoft.footprintmonitoring/profiles\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profile\" }\r\n ,\"microsoft.footprintmonitoring/profiles/experiments\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles experiment\" }\r\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoint\" }\r\n ,\"microsoft.footprintmonitoring/profiles/measurementendpoints/conditions\": { \"SingularDisplayName\": \"Microsoft.FootprintMonitoring profiles measurement endpoints condition\" }\r\n ,\"microsoft.gallery/myareas/galleryitems\": { \"SingularDisplayName\": \"Template\" }\r\n ,\"microsoft.genomics/accounts\": { \"SingularDisplayName\": \"Genomics account\" }\r\n ,\"microsoft.graph/azureadapplication\": { \"SingularDisplayName\": \"Entra application\" }\r\n ,\"microsoft.graph/azureadapplicationprototype\": { \"SingularDisplayName\": \"Microsoft.Graph Azure ad application prototype\" }\r\n ,\"microsoft.graphservices/accounts\": { \"SingularDisplayName\": \"Metered API account\" }\r\n ,\"microsoft.guestconfiguration/guestconfigurationassignments\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignment\" }\r\n ,\"microsoft.guestconfiguration/guestconfigurationassignments/reports\": { \"SingularDisplayName\": \"Microsoft.GuestConfiguration guest configuration assignments report\" }\r\n ,\"microsoft.hanaonazure/hanainstances\": { \"SingularDisplayName\": \"SAP HANA on Azure\" }\r\n ,\"microsoft.hanaonazure/sapmonitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP Solutions (classic)\" }\r\n ,\"microsoft.hardware/orders\": { \"SingularDisplayName\": \"Microsoft.Hardware order\" }\r\n ,\"microsoft.hardwaresecuritymodules/cloudhsmclusters\": { \"SingularDisplayName\": \"Azure Cloud HSM\" }\r\n ,\"microsoft.hdinsight/clusterpools\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster pool\" }\r\n ,\"microsoft.hdinsight/clusterpools/clusters\": { \"SingularDisplayName\": \"Azure HDInsight on AKS cluster\" }\r\n ,\"microsoft.hdinsight/clusterpools/clusters/instanceviews\": { \"SingularDisplayName\": \"Microsoft.HDInsight clusterpools clusters instance view\" }\r\n ,\"microsoft.hdinsight/clusters\": { \"SingularDisplayName\": \"HDInsight cluster\" }\r\n ,\"microsoft.healthbot/healthbots\": { \"SingularDisplayName\": \"Healthcare agent service\" }\r\n ,\"microsoft.healthcareapis/services\": { \"SingularDisplayName\": \"Azure API for FHIR\" }\r\n ,\"microsoft.healthcareapis/workspaces\": { \"SingularDisplayName\": \"Health Data Services workspace\" }\r\n ,\"microsoft.healthcareapis/workspaces/dicomservices\": { \"SingularDisplayName\": \"DICOM service\" }\r\n ,\"microsoft.healthcareapis/workspaces/fhirservices\": { \"SingularDisplayName\": \"FHIR service\" }\r\n ,\"microsoft.healthcareapis/workspaces/iotconnectors\": { \"SingularDisplayName\": \"MedTech service\" }\r\n ,\"microsoft.healthdataaiservices/deidservices\": { \"SingularDisplayName\": \"De-identification Service\" }\r\n ,\"microsoft.healthmodel/healthmodels\": { \"SingularDisplayName\": \"Health Model\" }\r\n ,\"microsoft.healthplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.HealthPlatform account\" }\r\n ,\"microsoft.help/diagnostics\": { \"SingularDisplayName\": \"Microsoft.Help diagnostic\" }\r\n ,\"microsoft.help/selfhelp\": { \"SingularDisplayName\": \"Microsoft.Help self help\" }\r\n ,\"microsoft.help/simplifiedsolutions\": { \"SingularDisplayName\": \"Microsoft.Help simplified solution\" }\r\n ,\"microsoft.help/solutions\": { \"SingularDisplayName\": \"Microsoft.Help solution\" }\r\n ,\"microsoft.help/troubleshooters\": { \"SingularDisplayName\": \"Microsoft.Help troubleshooter\" }\r\n ,\"microsoft.hpcworkbench/instances\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instance\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chamber\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/accessprofiles\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers access profile\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/filerequests\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file request\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/files\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers file\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/storages\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers storage\" }\r\n ,\"microsoft.hpcworkbench/instances/chambers/workloads\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances chambers workload\" }\r\n ,\"microsoft.hpcworkbench/instances/consortiums\": { \"SingularDisplayName\": \"Microsoft.HpcWorkbench instances consortium\" }\r\n ,\"microsoft.hybridcloud/cloudconnections\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connection\" }\r\n ,\"microsoft.hybridcloud/cloudconnectors\": { \"SingularDisplayName\": \"Microsoft.HybridCloud cloud connector\" }\r\n ,\"microsoft.hybridcompute/arcgatewayassociatedresources\": { \"SingularDisplayName\": \"Arc gateway associated resource\" }\r\n ,\"microsoft.hybridcompute/arcserverwithwac\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/gateways\": { \"SingularDisplayName\": \"Arc gateway\" }\r\n ,\"microsoft.hybridcompute/licenses\": { \"SingularDisplayName\": \"Extended Security Updates - Windows Server 2012/R2\" }\r\n ,\"microsoft.hybridcompute/machines\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machines/microsoft.awsconnector/ec2instances\": { \"SingularDisplayName\": \"Microsoft.AwsConnector ec2 instance\" }\r\n ,\"microsoft.hybridcompute/machines/microsoft.connectedvmwarevsphere/virtualmachineinstances\": { \"SingularDisplayName\": \"VMware + AVS virtual machine\" }\r\n ,\"microsoft.hybridcompute/machines/providers/guestconfigurationassignments\": { \"SingularDisplayName\": \"Guest Assignment\" }\r\n ,\"microsoft.hybridcompute/machinesesu\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinespaygo\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinessoftwareassurance\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/machinessovereign\": { \"SingularDisplayName\": \"Machine - Azure Arc\" }\r\n ,\"microsoft.hybridcompute/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Arc Private Link Scope\" }\r\n ,\"microsoft.hybridcompute/settings\": { \"SingularDisplayName\": \"Microsoft.HybridCompute setting\" }\r\n ,\"microsoft.hybridconnectivity/endpoints\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoint\" }\r\n ,\"microsoft.hybridconnectivity/endpoints/serviceconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity endpoints service configuration\" }\r\n ,\"microsoft.hybridconnectivity/publiccloudconnectors\": { \"SingularDisplayName\": \"Multicloud connector\" }\r\n ,\"microsoft.hybridconnectivity/solutionconfigurations\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configuration\" }\r\n ,\"microsoft.hybridconnectivity/solutionconfigurations/inventory\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution configurations inventory\" }\r\n ,\"microsoft.hybridconnectivity/solutiontypes\": { \"SingularDisplayName\": \"Microsoft.HybridConnectivity solution type\" }\r\n ,\"microsoft.hybridcontainerservice/kubernetesversions\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService kubernetes version\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instance\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/agentpools\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances agent pool\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances hybrid identity metadata\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusterinstances/upgradeprofiles\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService provisioned cluster instances upgrade profile\" }\r\n ,\"microsoft.hybridcontainerservice/provisionedclusters\": { \"SingularDisplayName\": \"Kubernetes hybrid - Azure Arc\" }\r\n ,\"microsoft.hybridcontainerservice/skus\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService SKU\" }\r\n ,\"microsoft.hybridcontainerservice/storagespaces\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService storage space\" }\r\n ,\"microsoft.hybridcontainerservice/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.HybridContainerService virtual network\" }\r\n ,\"microsoft.hybriddata/datamanagers\": { \"SingularDisplayName\": \"Microsoft.HybridData data manager\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data service\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definition\" }\r\n ,\"microsoft.hybriddata/datamanagers/dataservices/jobdefinitions/jobs\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data services job definitions job\" }\r\n ,\"microsoft.hybriddata/datamanagers/datastores\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store\" }\r\n ,\"microsoft.hybriddata/datamanagers/datastoretypes\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers data store type\" }\r\n ,\"microsoft.hybriddata/datamanagers/publickeys\": { \"SingularDisplayName\": \"Microsoft.HybridData data managers public key\" }\r\n ,\"microsoft.hybridnetwork/configurationgroupvalues\": { \"SingularDisplayName\": \"Configuration Group Value\" }\r\n ,\"microsoft.hybridnetwork/devices\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Device\" }\r\n ,\"microsoft.hybridnetwork/networkfunctions\": { \"SingularDisplayName\": \"Azure Network Function Manager ? Network Function\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publisher\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/artifactstores\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers artifact store\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers configuration group schema\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition group\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network function definition groups network function definition version\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design group\" }\r\n ,\"microsoft.hybridnetwork/proxypublishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork proxy publishers network service design groups network service design version\" }\r\n ,\"microsoft.hybridnetwork/publishers\": { \"SingularDisplayName\": \"Publisher\" }\r\n ,\"microsoft.hybridnetwork/publishers/artifactstores\": { \"SingularDisplayName\": \"Publisher Artifact Store\" }\r\n ,\"microsoft.hybridnetwork/publishers/artifactstores/artifactmanifests\": { \"SingularDisplayName\": \"Publisher Artifact Manifest\" }\r\n ,\"microsoft.hybridnetwork/publishers/configurationgroupschemas\": { \"SingularDisplayName\": \"Configuration Group Schema\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups\": { \"SingularDisplayName\": \"Network Function Definition\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkfunctiondefinitiongroups/networkfunctiondefinitionversions\": { \"SingularDisplayName\": \"Network Function Definition Version\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups\": { \"SingularDisplayName\": \"Network Service Design\" }\r\n ,\"microsoft.hybridnetwork/publishers/networkservicedesigngroups/networkservicedesignversions\": { \"SingularDisplayName\": \"Network Service Design Version\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management container\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rolloutsequences\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout sequence\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/rollouttiers\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers rollout tier\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specification\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollout\" }\r\n ,\"microsoft.hybridnetwork/servicemanagementcontainers/updatespecifications/rollouts/statuses\": { \"SingularDisplayName\": \"Microsoft.HybridNetwork service management containers update specifications rollouts statuse\" }\r\n ,\"microsoft.hybridnetwork/sitenetworkservices\": { \"SingularDisplayName\": \"Site Network Service\" }\r\n ,\"microsoft.hybridnetwork/sites\": { \"SingularDisplayName\": \"Site\" }\r\n })[tolower(id)]\r\n}\r\n", + "$fxv#10": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Prices |=========================================================================================================\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='Transforms Prices_raw into FOCUS 1.2.', folder='Prices')\r\nPrices_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n let prices = materialize(\r\n Prices_raw\r\n | extend PricingCurrency = coalesce(Currency, CurrencyCode) // CurrencyCode last as a fallback only\r\n | extend x_SkuId = coalesce(SkuId, SkuID)\r\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\r\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\r\n | extend x_SkuTerm = isoMonths(Term)\r\n | project-rename\r\n SkuMeter = MeterName,\r\n x_BaseUnitPrice = BasePrice,\r\n x_EffectivePeriodEnd = EffectiveEndDate,\r\n x_EffectivePeriodStart = EffectiveStartDate,\r\n x_PricingUnitDescription = UnitOfMeasure,\r\n x_SkuIncludedQuantity = IncludedQuantity,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuMeterType = MeterType,\r\n x_SkuOfferId = OfferID,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPriceType = PriceType,\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTier = TierMinimumUnits\r\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, real(null)) // UnitPrice for savings plan is not the on-demand unit price\r\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, real(null)) // MarketPrice for savings plan is not the list price\r\n | extend ChargeCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Usage',\r\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\r\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\r\n ''\r\n )\r\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\r\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\r\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\r\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\r\n //\r\n // Get latest ingested row based on the unique ID\r\n | extend x_IngestionTime = ingestion_time()\r\n );\r\n //\r\n // Meters for reservations and savings plans to identify commitment eligibility\r\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\r\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\r\n //\r\n // Copy list/base/contracted prices from on-demand SKUs\r\n prices\r\n | where x_SkuPriceType == 'SavingsPlan'\r\n // If we use join, specify the shuffle key\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\r\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\r\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\r\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\r\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\r\n //\r\n // Set CommitmentDiscountCategory for reuse\r\n | extend CommitmentDiscountCategory = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Usage',\r\n x_SkuPriceType == 'SavingsPlan', 'Spend',\r\n ''\r\n )\r\n //\r\n // Calculate commitment discount eligibility\r\n // TODO: Would a join be faster?\r\n // TODO: Check this to ensure it's correct\r\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\r\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\r\n //\r\n // TODO: Implement x_CommitmentDiscountNormalizedRatio\r\n | extend x_CommitmentDiscountNormalizedRatio = real(null)\r\n //\r\n // Add PricingUnit and x_PricingBlockSize\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\r\n | lookup kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n //\r\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, real(null)) // Savings plan prices are for the effective price, not the contracted price\r\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\r\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\r\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\r\n | project\r\n BillingAccountId = tolower(case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n BillingAccountId startswith '/', BillingAccountId,\r\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\r\n )),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\r\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\r\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\r\n ''\r\n ),\r\n CommitmentDiscountUnit = case(\r\n isempty(CommitmentDiscountCategory), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), PricingUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', PricingUnit),\r\n ''\r\n ),\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed',\r\n ''\r\n ),\r\n PricingCurrency,\r\n PricingUnit,\r\n SkuId = coalesce(ProductId, ProductID),\r\n SkuMeter,\r\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n strlen(x_BillingAccountId) > 32, 'MCA',\r\n strlen(x_BillingAccountId) < 32, 'EA',\r\n 'Unknown'\r\n ),\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\r\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory = case(\r\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_SkuDescription = Product,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\r\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\r\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\r\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\r\n}\r\n\r\n// Prices_final_v1_2 table\r\n.create-merge table Prices_final_v1_2 (\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n ChargeCategory: string,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountType: string,\r\n CommitmentDiscountUnit: string,\r\n ContractedUnitPrice: real,\r\n ListUnitPrice: real,\r\n PricingCategory: string,\r\n PricingCurrency: string, // Azure\r\n PricingUnit: string,\r\n SkuId: string,\r\n SkuMeter: string, // Azure\r\n SkuPriceId: string,\r\n SkuPriceIdv2: string, // Hubs add-on\r\n x_BaseUnitPrice: real, // Azure\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure MCA\r\n x_BillingProfileId: string, // Azure MCA\r\n x_CommitmentDiscountNormalizedRatio: real, // Hubs add-on\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_ContractedUnitPriceDiscount: real, // Hubs add-on\r\n x_ContractedUnitPriceDiscountPercent: real, // Hubs add-on\r\n x_EffectivePeriodEnd: datetime, // Azure\r\n x_EffectivePeriodStart: datetime, // Azure\r\n x_EffectiveUnitPrice: real, // Azure\r\n x_EffectiveUnitPriceDiscount: real, // Hubs add-on\r\n x_EffectiveUnitPriceDiscountPercent: real, // Hubs add-on\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_PricingBlockSize: real, // Hubs add-on\r\n x_PricingSubcategory: string, // Hubs add-on\r\n x_PricingUnitDescription: string, // Azure\r\n x_SkuDescription: string, // Azure\r\n x_SkuId: string, // Azure\r\n x_SkuIncludedQuantity: real, // Azure EA\r\n x_SkuMeterCategory: string, // Azure\r\n x_SkuMeterId: string, // Azure\r\n x_SkuMeterSubcategory: string, // Azure\r\n x_SkuMeterType: string, // Azure\r\n x_SkuPriceType: string, // Azure\r\n x_SkuProductId: string, // Azure\r\n x_SkuRegion: string, // Azure\r\n x_SkuServiceFamily: string, // Azure\r\n x_SkuOfferId: string, // Azure EA\r\n x_SkuPartNumber: string, // Azure EA\r\n x_SkuTerm: int, // Azure\r\n x_SkuTier: real, // Azure MCA\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_TotalUnitPriceDiscount: real, // Hubs add-on\r\n x_TotalUnitPriceDiscountPercent: real // Hubs add-on\r\n)\r\n\r\n// Update policy for Prices_raw -> Prices_final_v1_2\r\n.alter table Prices_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Prices_raw\",\r\n \"Query\": \"Prices_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Cost and usage |=================================================================================================\r\n// Supported versions:\r\n// - MS: 1.2-preview, 1.0, 1.0-preview(v1)\r\n// https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0\r\n// https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024\r\n// https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 \r\n// https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All costs transformed to FOCUS 1.2.', folder='Costs')\r\nCosts_transform_v1_2()\r\n{\r\n let checkString = (column: string, oldValue: string, newValue: string) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n let checkInt = (column: string, oldValue: int, newValue: int) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n let checkReal = (column: string, oldValue: real, newValue: real) {\r\n iff(oldValue == newValue, dynamic({}), bag_pack(column, oldValue))\r\n };\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n Costs_raw\r\n //\r\n // Dedupe rows\r\n | extend x_IngestionTime = ingestion_time()\r\n | extend x_ChargeId = ''\r\n // TODO: Consider adding a unique charge ID per row\r\n // hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // // 1. Resource hierarchy (including resource name), highest to lowest\r\n // BillingAccountId,\r\n // x_InvoiceSectionId,\r\n // x_AccountOwnerId,\r\n // SubAccountId,\r\n // x_ResourceGroupName,\r\n // ResourceName,\r\n // // 2. Resource details\r\n // ResourceId,\r\n // RegionId,\r\n // Tags,\r\n // CommitmentDiscountId,\r\n // x_CostCenter,\r\n // // 4. Meter details\r\n // SkuPriceId,\r\n // x_SkuMeterId,\r\n // x_SkuPartNumber,\r\n // x_SkuOfferId,\r\n // x_SkuDetails,\r\n // // 5. Date\r\n // ChargePeriodStart\r\n // ))\r\n //\r\n // Identify data quality issues\r\n // TODO: Remove x_SourceChanges in v1_3 (or later)\r\n | extend x_SourceChanges = trim_end(',', strcat(\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\r\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\r\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\r\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\r\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\r\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\r\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\r\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\r\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\r\n 'XEffectiveUnitPriceRoundingError,', ''),\r\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\r\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\r\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\r\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\r\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\r\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\r\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\r\n ))\r\n //\r\n // Handle provider columns that moved to FOCUS\r\n | extend PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency)\r\n //\r\n // Backup original prices/costs before the merge\r\n | extend old_ContractedCost = ContractedCost\r\n | extend old_ContractedUnitPrice = ContractedUnitPrice\r\n | extend old_ListCost = ListCost\r\n | extend old_ListUnitPrice = ListUnitPrice\r\n | extend old_x_EffectiveUnitPrice = x_EffectiveUnitPrice\r\n //\r\n // Fix columns needed in other changes\r\n | extend old_ProviderName = ProviderName, ProviderName = case(\r\n isnotempty(ProviderName), ProviderName,\r\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\r\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\r\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\r\n ''\r\n )\r\n //\r\n // Identify source\r\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\r\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\r\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\r\n ''\r\n ))\r\n // Append version check error code\r\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\r\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\r\n )\r\n //\r\n // Fix quantities\r\n | extend old_PricingQuantity = PricingQuantity, PricingQuantity = case(\r\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\r\n PricingQuantity\r\n )\r\n | extend old_ConsumedQuantity = ConsumedQuantity, ConsumedQuantity = case(\r\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\r\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\r\n ConsumedQuantity\r\n )\r\n //\r\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\r\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\r\n and (isempty(ListUnitPrice) or isempty(ContractedUnitPrice) or ListUnitPrice == 0 or ContractedUnitPrice == 0)\r\n and x_EffectiveUnitPrice != 0\r\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\r\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\r\n | as allCosts\r\n | where tmp_MissingPrices\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | as costsWithMissingPrices\r\n | join kind=leftouter (\r\n Prices_final_v1_2\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\r\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\r\n ) on tmp_ReservationPriceLookupKey\r\n //\r\n // Select the best price to use for each row\r\n | extend x_EffectiveUnitPrice = case(\r\n // If price is a rounding error away from the billed price, use the billed price\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\r\n // If price is a rounding error away from the contracted price, use the contracted price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ContractedUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\r\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ListUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // Otherwise, assume the contracted price is the same as list price to support aggregations\r\n ContractedUnitPrice\r\n )\r\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\r\n | extend ContractedCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\r\n // ContractedCost is 0 in all other scenarios...\r\n // If 0 and there's a billed cost and prices are the same, use BilledCost\r\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\r\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\r\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume EffectiveCost\r\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ContractedCost\r\n )\r\n | extend ListCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\r\n // ListCost is 0 in all other scenarios...\r\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\r\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume ContractedCost\r\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ListCost\r\n )\r\n // Merge the rest of the unmodified cost records and remove excess columns\r\n | union (allCosts | where not(tmp_MissingPrices))\r\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\r\n //\r\n | extend SkuPriceDetails = parse_json(SkuPriceDetails)\r\n | extend Tags = parse_json(Tags)\r\n | extend x_SkuDetails = parse_json(x_SkuDetails)\r\n //\r\n // Handle FOCUS 1.0-preview\r\n | extend old_ChargeSubcategory = ChargeSubcategory\r\n | extend old_ChargeCategory = ChargeCategory, ChargeCategory = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Credit', 'Credit',\r\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\r\n ChargeCategory\r\n )\r\n | extend old_ChargeClass = ChargeClass, ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass)\r\n //\r\n // Populate CapacityReservationId when not specified\r\n | extend CapacityReservationId = coalesce(CapacityReservationId, tostring(coalesce(x_SkuDetails.VMCapacityReservationId, SkuPriceDetails.VMCapacityReservationId, SkuPriceDetails.x_VMCapacityReservationId)))\r\n | extend old_CapacityReservationStatus = CapacityReservationStatus, CapacityReservationStatus = case(\r\n isempty(CapacityReservationId), '',\r\n isnotempty(CapacityReservationStatus), CapacityReservationStatus,\r\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\r\n 'Used'\r\n )\r\n //\r\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\r\n | extend old_ChargeFrequency = ChargeFrequency, ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency)\r\n //\r\n // Commitment discounts\r\n | extend x_CommitmentDiscountNormalizedRatio = case(\r\n // Calculate from CommitmentDiscountQuantity, if specified\r\n isnotempty(CommitmentDiscountQuantity) and CommitmentDiscountQuantity != 0, CommitmentDiscountQuantity / PricingQuantity / coalesce(x_PricingBlockSize, real(1)),\r\n // Not applicable\r\n isempty(CommitmentDiscountStatus), real(null),\r\n // Parse from SKU details if not specified explicitly\r\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, SkuPriceDetails.RINormalizationRatio, SkuPriceDetails.x_RINormalizationRatio, dynamic(1)))\r\n )\r\n | extend old_CommitmentDiscountQuantity = CommitmentDiscountQuantity, CommitmentDiscountQuantity = case(\r\n // FOCUS 1.2\r\n isnotempty(CommitmentDiscountQuantity), CommitmentDiscountQuantity,\r\n // FOCUS 1.0-preview, 1.0\r\n isempty(CommitmentDiscountStatus), real(null),\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\r\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\r\n real(null)\r\n )\r\n | extend old_CommitmentDiscountUnit = CommitmentDiscountUnit, CommitmentDiscountUnit = case(\r\n // FOCUS 1.2\r\n isnotempty(CommitmentDiscountUnit), CommitmentDiscountUnit,\r\n // FOCUS 1.0\r\n isempty(CommitmentDiscountQuantity), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\r\n ''\r\n )\r\n | extend old_CommitmentDiscountStatus = CommitmentDiscountStatus, CommitmentDiscountStatus = case(\r\n // FOCUS 1.0+\r\n isnotempty(CommitmentDiscountStatus), CommitmentDiscountStatus,\r\n // FOCUS 1.0-preview\r\n ChargeSubcategory == 'Used Commitment', 'Used',\r\n ChargeSubcategory == 'Unused Commitment', 'Unused',\r\n ''\r\n )\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n //\r\n // Pricing\r\n | extend old_x_AmortizationClass = x_AmortizationClass, x_AmortizationClass = case(\r\n // FOCUS 1.2\r\n isnotempty(x_AmortizationClass), x_AmortizationClass,\r\n // FOCUS 1.0-preview+\r\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\r\n ''\r\n )\r\n | extend old_PricingCategory = PricingCategory, PricingCategory = case(\r\n // FOCUS 1.0+\r\n isnotempty(PricingCategory), PricingCategory,\r\n // FOCUS 1.0-preview\r\n PricingCategory == 'On-Demand', 'Standard',\r\n PricingCategory == 'Commitment-Based', 'Committed',\r\n ''\r\n )\r\n //\r\n // Commitment discount utilization\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n //\r\n // BUG: Fix ContractedCost that has bad values\r\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\r\n //\r\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\r\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), real(null))\r\n | extend old_ConsumedUnit = ConsumedUnit, ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\r\n //\r\n // Convert IDs to lowercase for consistency\r\n | extend BillingAccountId = tolower(BillingAccountId)\r\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\r\n //\r\n // BUG: Remove EffectiveCost for commitment discount purchases\r\n | extend old_EffectiveCost = EffectiveCost, EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), EffectiveCost)\r\n | extend old_x_EffectiveCostInUsd = x_EffectiveCostInUsd, x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), real(0), x_EffectiveCostInUsd)\r\n //\r\n // Clean up resource columns\r\n | extend old_ResourceId = ResourceId, ResourceId = case(\r\n isnotempty(ResourceId), ResourceId,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\r\n ResourceId\r\n )\r\n | extend old_ResourceName = ResourceName, ResourceName = tolower(case(\r\n isnotempty(ResourceName), ResourceName,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\r\n ResourceName\r\n ))\r\n | extend old_x_ResourceType = x_ResourceType, x_ResourceType = case(\r\n isnotempty(x_ResourceType), x_ResourceType,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\r\n x_ResourceType\r\n )\r\n | extend old_ResourceType = ResourceType, ResourceType = case(\r\n // Use existing resource type display name unless it's an internal resource type ID\r\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\r\n // Use CommitmentDiscountType for commitment discount purchases\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\r\n // Look up display name from internal type\r\n isnotempty(x_ResourceType), coalesce(tostring(resource_type(x_ResourceType).SingularDisplayName), ResourceType, x_ResourceType),\r\n ResourceType\r\n )\r\n //\r\n // Handle missing values\r\n | extend old_PublisherName = PublisherName, PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, '')\r\n //\r\n // Handle FOCUS 1.0-preview Region column\r\n | extend old_Region = Region\r\n | extend old_RegionId = RegionId, RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region))\r\n | extend RegionName = coalesce(RegionName, Region)\r\n //\r\n // SKU properties\r\n | extend x_SkuCoreCount = toint(coalesce(SkuPriceDetails.CoreCount, SkuPriceDetails.x_VCPUs, x_SkuDetails.VCPUs, SkuPriceDetails.x_VCores, x_SkuDetails.VCores, SkuPriceDetails.x_vCores, x_SkuDetails.vCores))\r\n | extend x_SkuInstanceType = tostring(coalesce(SkuPriceDetails.InstanceType, SkuPriceDetails.x_ServiceType, x_SkuDetails.ServiceType, SkuPriceDetails.x_ServerSku, x_SkuDetails.ServerSku))\r\n | extend x_SkuOperatingSystem = case(\r\n isnotempty(SkuPriceDetails.OperatingSystem), SkuPriceDetails.OperatingSystem,\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Canonical', 'Linux',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType)\r\n )\r\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\r\n | extend SkuPriceDetails = case(\r\n // FOCUS 1.2\r\n isnotempty(SkuPriceDetails), SkuPriceDetails,\r\n // FOCUS 1.0-preview, 1.0\r\n parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\r\n // Prefix all keys with x_ first to avoid double-prefixing\r\n , @'([\\{,])\"', @'\\1\"x_')\r\n // CoreCount for number of CPUs/vCPUs/cores/vCores\r\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\r\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\r\n // TODO: DiskSpace for disk size in GiB\r\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\r\n // TODO: GpuCount for the number of GPUs\r\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\r\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\r\n // TODO: InstanceSeries for the size family/series\r\n // TODO: MemorySize for the RAM in GiB\r\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\r\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\r\n // OperatingSystem for the OS name\r\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\r\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\r\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\r\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\r\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\r\n )\r\n )\r\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\r\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\r\n SkuPriceDetails)\r\n //\r\n // Azure Hybrid Benefit\r\n | extend tmp_SqlAhb = tolower(coalesce(x_SkuDetails.AHB, SkuPriceDetails.x_AHB))\r\n | extend x_SkuLicenseType = case(\r\n ChargeCategory != 'Usage', '',\r\n x_SkuMeterCategory in ('Virtual Machines', 'Virtual Machine Licenses') and (x_SkuMeterSubcategory contains 'Windows' or coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL'), 'Windows Server',\r\n isnotempty(tmp_SqlAhb) or x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\r\n ''\r\n )\r\n | extend x_SkuLicenseStatus = case(\r\n isempty(x_SkuLicenseType), '',\r\n coalesce(SkuPriceDetails.x_ImageType, x_SkuDetails.ImageType) == 'Windows Server BYOL' or tmp_SqlAhb == 'true' or x_SkuMeterSubcategory contains 'Azure Hybrid Benefit', 'Enabled',\r\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not Enabled',\r\n ''\r\n )\r\n | extend x_SkuLicenseQuantity = case(\r\n isempty(x_SkuCoreCount) or isempty(x_SkuLicenseType), int(null),\r\n x_SkuCoreCount <= 8, int(8),\r\n x_SkuCoreCount > 8, x_SkuCoreCount,\r\n int(null)\r\n )\r\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\r\n //\r\n // Savings\r\n | extend x_CommitmentDiscountSavings = iff(isempty(ContractedCost) or ContractedCost == 0 or ContractedCost - EffectiveCost < 0.0001, real(0), ContractedCost - EffectiveCost)\r\n | extend x_NegotiatedDiscountSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - ContractedCost < 0.0001, real(0), ListCost - ContractedCost)\r\n | extend x_TotalSavings = iff(isempty(ListCost) or ListCost == 0 or ListCost - EffectiveCost < 0.0001, real(0), ListCost - EffectiveCost)\r\n | extend x_CommitmentDiscountPercent = iff(isempty(ContractedUnitPrice) or ContractedUnitPrice == 0 or ContractedUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\r\n | extend x_NegotiatedDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - ContractedUnitPrice < 0.0001, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\r\n | extend x_TotalDiscountPercent = iff(isempty(ListUnitPrice) or ListUnitPrice == 0 or ListUnitPrice - x_EffectiveUnitPrice < 0.0001, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\r\n //\r\n // Minor fixes\r\n | extend old_BillingPeriodEnd = BillingPeriodEnd, BillingPeriodEnd = startofmonth(BillingPeriodEnd)\r\n | extend old_BillingPeriodStart = BillingPeriodStart, BillingPeriodStart = startofmonth(BillingPeriodStart)\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n CapacityReservationId,\r\n CapacityReservationStatus,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\r\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\r\n EffectiveCost,\r\n InvoiceId = coalesce(InvoiceId, x_InvoiceId),\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory, // TODO: Populate ServiceSubcategory from ServiceName when missing\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceDetails,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName = iff(isempty(SubAccountId), '', SubAccountName),\r\n SubAccountType,\r\n Tags,\r\n x_AccountId = iff(x_AccountId == '-2', '', x_AccountId),\r\n x_AccountName = iff(x_AccountId == '-2', '', x_AccountName),\r\n x_AccountOwnerId = iff(x_AccountId == '-2', '', x_AccountOwnerId),\r\n x_AmortizationClass,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\r\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\r\n ProviderName\r\n ),\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingItemCode,\r\n x_BillingItemName,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountPercent,\r\n x_CommitmentDiscountSavings,\r\n x_CommitmentDiscountSpendEligibility = '', // TODO: Add x_CommitmentDiscountSpendEligibility for Costs\r\n x_CommitmentDiscountUsageEligibility = '', // TODO: Add x_CommitmentDiscountUsageEligibility for Costs\r\n x_CommitmentDiscountUtilizationAmount,\r\n x_CommitmentDiscountUtilizationPotential,\r\n x_CommodityCode,\r\n x_CommodityName,\r\n x_ComponentName,\r\n x_ComponentType,\r\n x_ConsumedCoreHours,\r\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd),\r\n x_CostAllocationRuleName,\r\n x_CostCategories = parse_json(x_CostCategories),\r\n x_CostCenter,\r\n x_CostType,\r\n x_Credits = parse_json(x_Credits),\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount = parse_json(x_Discount),\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InstanceID,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId = case(\r\n x_InvoiceSectionId == '-2', '',\r\n x_InvoiceSectionId\r\n ),\r\n x_InvoiceSectionName = case(\r\n x_InvoiceSectionName == 'Unassigned', '',\r\n x_InvoiceSectionName\r\n ),\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_NegotiatedDiscountPercent,\r\n x_NegotiatedDiscountSavings,\r\n x_Operation,\r\n x_OwnerAccountID,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription = iff(x_PricingUnitDescription == 'Unassigned', '', x_PricingUnitDescription),\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName = tolower(x_ResourceGroupName),\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServiceModel, // TODO: Populate from ServiceName when missing\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuCoreCount,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuInstanceType,\r\n x_SkuIsCreditEligible,\r\n x_SkuLicenseQuantity,\r\n x_SkuLicenseStatus,\r\n x_SkuLicenseType,\r\n x_SkuLicenseUnit,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOperatingSystem,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuPlanName,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceValues = bag_merge(\r\n checkString('BillingPeriodEnd', old_BillingPeriodEnd, BillingPeriodEnd),\r\n checkString('BillingPeriodStart', old_BillingPeriodStart, BillingPeriodStart),\r\n checkString('CapacityReservationStatus', old_CapacityReservationStatus, CapacityReservationStatus),\r\n checkString('ChargeCategory', old_ChargeCategory, ChargeCategory),\r\n checkString('ChargeClass', old_ChargeClass, ChargeClass),\r\n checkString('ChargeSubcategory', old_ChargeSubcategory, ''), // Not included in final schema; use empty string\r\n checkString('ChargeFrequency', old_ChargeFrequency, ChargeFrequency),\r\n checkReal('CommitmentDiscountQuantity', old_CommitmentDiscountQuantity, CommitmentDiscountQuantity),\r\n checkString('CommitmentDiscountUnit', old_CommitmentDiscountUnit, CommitmentDiscountUnit),\r\n checkString('CommitmentDiscountStatus', old_CommitmentDiscountStatus, CommitmentDiscountStatus),\r\n checkReal('ConsumedQuantity', old_ConsumedQuantity, ConsumedQuantity),\r\n checkString('ConsumedUnit', old_ConsumedUnit, ConsumedUnit),\r\n checkReal('ContractedCost', old_ContractedCost, ContractedCost),\r\n checkReal('ContractedUnitPrice', old_ContractedUnitPrice, ContractedUnitPrice),\r\n checkReal('EffectiveCost', old_EffectiveCost, EffectiveCost),\r\n checkReal('ListCost', old_ListCost, ListCost),\r\n checkReal('ListUnitPrice', old_ListUnitPrice, ListUnitPrice),\r\n checkString('PricingCategory', old_PricingCategory, PricingCategory),\r\n checkReal('PricingQuantity', old_PricingQuantity, PricingQuantity),\r\n checkString('ProviderName', old_ProviderName, ProviderName),\r\n checkString('PublisherName', old_PublisherName, PublisherName),\r\n checkString('Region', old_Region, ''), // Not included in final schema; use empty string\r\n checkString('RegionId', old_RegionId, RegionId),\r\n checkString('ResourceId', old_ResourceId, ResourceId),\r\n checkString('ResourceName', old_ResourceName, ResourceName),\r\n checkString('ResourceType', old_ResourceType, ResourceType),\r\n checkString('x_AmortizationClass', old_x_AmortizationClass, x_AmortizationClass),\r\n checkReal('x_EffectiveCostInUsd', old_x_EffectiveCostInUsd, x_EffectiveCostInUsd),\r\n checkReal('x_EffectiveUnitPrice', old_x_EffectiveUnitPrice, x_EffectiveUnitPrice),\r\n checkString('x_ResourceType', old_x_ResourceType, x_ResourceType)\r\n ),\r\n x_SourceVersion,\r\n x_SubproductName,\r\n x_TotalDiscountPercent,\r\n x_TotalSavings,\r\n x_UsageType\r\n}\r\n\r\n// Costs_final_v1_2 table\r\n.create-merge table Costs_final_v1_2 (\r\n AvailabilityZone: string,\r\n BilledCost: real,\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingAccountType: string,\r\n BillingCurrency: string,\r\n BillingPeriodEnd: datetime,\r\n BillingPeriodStart: datetime,\r\n CapacityReservationId: string,\r\n CapacityReservationStatus: string,\r\n ChargeCategory: string,\r\n ChargeClass: string,\r\n ChargeDescription: string,\r\n ChargeFrequency: string,\r\n ChargePeriodEnd: datetime,\r\n ChargePeriodStart: datetime,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountId: string,\r\n CommitmentDiscountName: string,\r\n CommitmentDiscountQuantity: real,\r\n CommitmentDiscountStatus: string,\r\n CommitmentDiscountType: string,\r\n CommitmentDiscountUnit: string,\r\n ConsumedQuantity: real,\r\n ConsumedUnit: string,\r\n ContractedCost: real,\r\n ContractedUnitPrice: real,\r\n EffectiveCost: real,\r\n InvoiceId: string,\r\n InvoiceIssuerName: string,\r\n ListCost: real,\r\n ListUnitPrice: real,\r\n PricingCategory: string,\r\n PricingCurrency: string,\r\n PricingQuantity: real,\r\n PricingUnit: string,\r\n ProviderName: string,\r\n PublisherName: string,\r\n RegionId: string,\r\n RegionName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n ServiceCategory: string,\r\n ServiceName: string,\r\n ServiceSubcategory: string,\r\n SkuId: string,\r\n SkuMeter: string,\r\n SkuPriceDetails: dynamic,\r\n SkuPriceId: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n SubAccountType: string,\r\n Tags: dynamic,\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_AmortizationClass: string, // Azure 1.2-preview+\r\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingItemCode: string, // Alibaba 1.0\r\n x_BillingItemName: string, // Alibaba 1.0\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_CommitmentDiscountNormalizedRatio: real, // Azure 1.2-preview+\r\n x_CommitmentDiscountPercent: real, // Hubs add-on\r\n x_CommitmentDiscountSavings: real, // Hubs add-on\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUtilizationAmount: real, // Hubs add-on\r\n x_CommitmentDiscountUtilizationPotential: real, // Hubs add-on\r\n x_CommodityCode: string, // Alibaba 1.0\r\n x_CommodityName: string, // Alibaba 1.0\r\n x_ComponentName: string, // Tencent 1.0\r\n x_ComponentType: string, // Tencent 1.0\r\n x_ConsumedCoreHours: real, // Hubs add-on\r\n x_ContractedCostInUsd: real, // Azure 1.0+\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_CostType: string, // GCP Jan 2024\r\n x_Credits: dynamic, // GCP Jan 2024\r\n x_CurrencyConversionRate: real, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: dynamic, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_InstanceID: string, // Alibaba 1.0\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_NegotiatedDiscountPercent:real, // Hubs add-on\r\n x_NegotiatedDiscountSavings:real, // Hubs add-on\r\n x_Operation: string, // AWS 1.0\r\n x_OwnerAccountID: string, // Tencent 1.0\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServiceModel: string, // Azure 1.2-preview+\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuCoreCount: int, // Hubs add-on\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\r\n x_SkuInstanceType: string, // Hubs add-on\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuLicenseQuantity: int, // Hubs add-on\r\n x_SkuLicenseStatus: string, // Hubs add-on\r\n x_SkuLicenseType: string, // Hubs add-on\r\n x_SkuLicenseUnit: string, // Hubs add-on\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOperatingSystem: string, // Hubs add-on\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuPlanName: string, // Azure 1.2-preview+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceChanges: string, // Hubs add-on\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceValues: dynamic, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubproductName: string, // Tencent 1.0\r\n x_TotalDiscountPercent: real, // Hubs add-on\r\n x_TotalSavings: real, // Hubs add-on\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Update policy for Costs_raw -> Costs_final_v1_2 table\r\n.alter table Costs_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Costs_raw\",\r\n \"Query\": \"Costs_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Actual costs |===================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\r\nActualCosts_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n ActualCosts_raw\r\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodEnd = Date + 1d,\r\n ChargePeriodStart = Date,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId = '',\r\n SkuMeter = MeterName,\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentType = '',\r\n x_ComponentName = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = '',\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel,\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for ActualCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"ActualCosts_raw\",\r\n \"Query\": \"ActualCosts_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Amortized costs |================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='ActualCost exports transformed to FOCUS 1.2.', folder='Costs')\r\nAmortizedCosts_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n AmortizedCosts_raw\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: PublisherName, x_PublisherCategory, x_Environment\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory), take_any(ServiceSubcategory), take_any(x_ServiceModel) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodEnd = Date + 1d,\r\n ChargePeriodStart = Date,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId = '',\r\n SkuMeter = MeterName,\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = iff(isempty(SubscriptionId), '', SubscriptionName),\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentType = '',\r\n x_ComponentName = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = '',\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel,\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for AmortizedCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"AmortizedCosts_raw\",\r\n \"Query\": \"AmortizedCosts_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All commitment discount usage transformed to FOCUS 1.2. This includes reservationdeatils_raw.', folder='Commitment discounts')\r\nCommitmentDiscountUsage_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n CommitmentDiscountUsage_raw\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Handle resource columns\r\n | extend ResourceId = tolower(InstanceId)\r\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\r\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\r\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\r\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\r\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\r\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n ChargePeriodEnd = UsageDate + 1d,\r\n ChargePeriodStart = UsageDate,\r\n CommitmentDiscountCategory = 'Usage',\r\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\r\n CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\r\n CommitmentDiscountType = 'Reservation',\r\n CommitmentDiscountUnit = case(\r\n InstanceFlexibilityRatio == 1, 'Hours',\r\n InstanceFlexibilityRatio != 1, 'Normalized Hours',\r\n ''\r\n ),\r\n ConsumedQuantity = UsedHours,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\r\n x_CommitmentDiscountCommittedAmount = ReservedHours,\r\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\r\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\r\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\r\n x_IngestionTime = ingestion_time(),\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n // x_RowId = hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // CommitmentDiscountId,\r\n // ResourceId,\r\n // ChargePeriodStart\r\n // )),\r\n x_ServiceModel,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\r\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\r\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\r\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\r\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\r\n}\r\n\r\n// CommitmentDiscountUsage_final_v1_2 table\r\n.create-merge table CommitmentDiscountUsage_final_v1_2 (\r\n ChargePeriodEnd: datetime, // Hubs add-on\r\n ChargePeriodStart: datetime, // MS 2023-03-01\r\n CommitmentDiscountCategory: string, // Hubs add-on\r\n CommitmentDiscountId: string, // MS 2023-03-01\r\n CommitmentDiscountQuantity: real, // MS 2023-03-01\r\n CommitmentDiscountType: string, // Hubs add-on\r\n CommitmentDiscountUnit: string, // Hubs add-on\r\n ConsumedQuantity: real, // MS 2023-03-01\r\n ProviderName: string, // Hubs add-on\r\n ResourceId: string, // MS 2023-03-01\r\n ResourceName: string, // Hubs add-on\r\n ResourceType: string, // Hubs add-on\r\n ServiceCategory: string, // Hubs add-on\r\n ServiceName: string, // Hubs add-on\r\n ServiceSubcategory: string, // Hubs add-on\r\n SubAccountId: string, // Hubs add-on\r\n x_CommitmentDiscountCommittedCount: real, // MS 2023-03-01\r\n x_CommitmentDiscountCommittedAmount: real, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedRatio: real, // MS 2023-03-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_ResourceGroupName: string, // Hubs add-on\r\n x_ResourceType: string, // Hubs add-on\r\n x_ServiceModel: string, // Hubs add-on\r\n x_SkuOrderId: string, // MS 2023-03-01\r\n x_SkuSize: string, // MS 2023-03-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string // Hubs add-on\r\n)\r\n\r\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_2 table\r\n.alter table CommitmentDiscountUsage_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"CommitmentDiscountUsage_raw\",\r\n \"Query\": \"CommitmentDiscountUsage_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All recommendations transformed to FOCUS 1.2.', folder='Recommendations')\r\nRecommendations_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Recommendations_raw\r\n | extend x_IngestionTime = ingestion_time()\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Convert JSON cost columns to real\r\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\r\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\r\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), toreal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\r\n //\r\n // Build recommendation details\r\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\r\n | extend x_RecommendationDetails = case(\r\n // Use incoming x_RecommendationDetails first\r\n isnotempty(x_RecommendationDetails), x_RecommendationDetails,\r\n // Create one for reservation recommendations if needed\r\n x_SourceType == 'ReservationRecommendations', bag_pack(\r\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\r\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\r\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\r\n 'CommitmentDiscountResourceType', ResourceType,\r\n 'CommitmentDiscountScope', Scope,\r\n 'LookbackPeriodDuration', case(\r\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\r\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\r\n ''\r\n ),\r\n 'LookbackPeriodStart', FirstUsageDate,\r\n 'RecommendedQuantity', RecommendedQuantity,\r\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\r\n 'RegionId', Location,\r\n 'RegionName', RegionName,\r\n 'SkuMeterId', MeterId,\r\n 'SkuPriceDetails', SkuProperties,\r\n 'SkuSize', coalesce(SKU, SkuName),\r\n 'SkuTerm', isoMonths(Term)\r\n ),\r\n dynamic({})\r\n )\r\n //\r\n // Prefer specified date, then fall back to generating a date based on reservation recommendation lookback period, then validate to ensure it's not in the future\r\n | extend x_RecommendationDate = coalesce(x_RecommendationDate, FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d))\r\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\r\n //\r\n | project\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n SubAccountId = coalesce(SubAccountId, iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), '')),\r\n SubAccountName,\r\n x_EffectiveCostAfter = coalesce(x_EffectiveCostAfter, TotalCostWithReservedInstances),\r\n x_EffectiveCostBefore = coalesce(x_EffectiveCostBefore, CostWithNoReservedInstances),\r\n x_EffectiveCostSavings = coalesce(x_EffectiveCostSavings, NetSavings),\r\n x_IngestionTime,\r\n x_RecommendationCategory, // TODO: Set for reservation recommendations\r\n x_RecommendationDate,\r\n x_RecommendationDescription,\r\n x_RecommendationDetails,\r\n x_RecommendationId, // TODO: Set for reservation recommendations\r\n x_ResourceGroupName,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n// Recommendations_final_v1_2 table\r\n.create-merge table Recommendations_final_v1_2 (\r\n ProviderName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n x_EffectiveCostAfter: real,\r\n x_EffectiveCostBefore: real,\r\n x_EffectiveCostSavings: real,\r\n x_IngestionTime: datetime,\r\n x_RecommendationCategory: string,\r\n x_RecommendationDate: datetime,\r\n x_RecommendationDescription: string,\r\n x_RecommendationDetails: dynamic,\r\n x_RecommendationId: string,\r\n x_ResourceGroupName: string,\r\n x_SourceName: string,\r\n x_SourceProvider: string,\r\n x_SourceType: string,\r\n x_SourceVersion: string\r\n)\r\n\r\n// Update policy for Recommendations_raw -> Recommendations_final_v1_2 table\r\n.alter table Recommendations_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Recommendations_raw\",\r\n \"Query\": \"Recommendations_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_transform_v1_2 function\r\n.create-or-alter function\r\nwith (docstring='All transactions transformed to FOCUS 1.2.', folder='Transactions')\r\nTransactions_transform_v1_2()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', int(null),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Transactions_raw\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Handle BillingPeriodStart/End\r\n | extend BillingMonth = tostring(BillingMonth)\r\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\r\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n BilledCost = Amount,\r\n BillingAccountId = case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\r\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\r\n ''\r\n ),\r\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\r\n BillingCurrency = Currency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory = case(\r\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = case(\r\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\r\n EventType == 'Refund', 'Correction',\r\n ''\r\n ),\r\n ChargeDescription = Description,\r\n ChargeFrequency = case(\r\n BillingFrequency == 'OneTime', 'One-Time',\r\n BillingFrequency == 'Recurring', 'Recurring',\r\n BillingFrequency\r\n ),\r\n ChargePeriodStart = EventDate,\r\n InvoiceId,\r\n PricingQuantity = Quantity,\r\n PricingUnit = 'Reservations',\r\n ProviderName,\r\n RegionId = Region,\r\n RegionName = Region,\r\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\r\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerEmail,\r\n x_CostCenter = CostCenter,\r\n x_InvoiceNumber = Invoice,\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\r\n x_IngestionTime = ingestion_time(),\r\n x_MonetaryCommitment = MonetaryCommitment,\r\n x_Overage = Overage,\r\n x_PurchasingBillingAccountId = PurchasingEnrollment,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuOrderName = ReservationOrderName,\r\n x_SkuSize = ArmSkuName,\r\n x_SkuTerm = isoMonths(Term),\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId = PurchasingSubscriptionGuid,\r\n x_TransactionType = EventType\r\n}\r\n\r\n// Transactions_final_v1_2 table\r\n.create-merge table Transactions_final_v1_2 (\r\n BilledCost: real, // MS CM EA+MCA 2023-05-01\r\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\r\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\r\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n ChargeCategory: string, // Hubs add-on\r\n ChargeClass: string, // Hubs add-on\r\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\r\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\r\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n InvoiceId: string, // MS CM MCA 2023-05-01\r\n PricingQuantity: real, // MS CM EA+MCA 2023-05-01\r\n PricingUnit: string, // Hubs add-on\r\n ProviderName: string, // Hubs add-on\r\n RegionId: string, // MS CM EA+MCA 2023-05-01\r\n RegionName: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\r\n x_AccountName: string, // MS CM EA 2023-05-01\r\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\r\n x_CostCenter: string, // MS CM EA 2023-05-01\r\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_MonetaryCommitment: real, // MS CM EA 2023-05-01\r\n x_Overage: real, // MS CM EA 2023-05-01\r\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\r\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\r\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\r\n)\r\n\r\n// Update policy for Transactions_raw -> Transactions_final_v1_2 table\r\n.alter table Transactions_final_v1_2 policy update\r\n```\r\n[{\r\n \"IsEnabled\": true,\r\n \"Source\": \"Transactions_raw\",\r\n \"Query\": \"Transactions_transform_v1_2()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n", + "$fxv#11": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Common utility functions\r\n//\r\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\r\n//======================================================================================================================\r\n\r\n\r\n//===| Date functions |=================================================================================================\r\n\r\n// monthstring\r\n.create-or-alter function \r\nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \r\nmonthstring(['date']: datetime, length: int = 9)\r\n{\r\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\r\n}\r\n\r\n// datestring\r\n.create-or-alter function \r\nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \r\ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n let month = (d: datetime) { monthstring(d, 3) };\r\n let endDate = iff(end == datetime('0001-01-01'), start, end);\r\n let sameDate = startofday(start) == startofday(endDate);\r\n let sameMonth = startofmonth(start) == startofmonth(endDate);\r\n let sameYear = startofyear(start) == startofyear(endDate);\r\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\r\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\r\n let currentYear = sameYear and startofyear(start) == startofyear(now());\r\n case(\r\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\r\n fullYear,\r\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\r\n // 1 full mo, same year | Mmm yyyy\r\n fullMonth and sameMonth and sameYear,\r\n strcat(month(start), ' ', getyear(start)),\r\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\r\n fullMonth and sameYear,\r\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\r\n fullMonth and not(sameYear),\r\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\r\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\r\n sameDate,\r\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\r\n not(fullMonth) and sameMonth and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\r\n not(fullMonth) and not(sameMonth) and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\r\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\r\n )\r\n}\r\n\r\n// daterange\r\n.create-or-alter function \r\nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \r\ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n datestring(start, end)\r\n}\r\n\r\n// monthsago\r\n.create-or-alter function \r\nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\r\nmonthsago(months: int)\r\n{\r\n datetime_add('month', -months, startofmonth(now()))\r\n}\r\n\r\n\r\n//===| Number functions |===============================================================================================\r\n// NOTE: Must be defined before string converters\r\n\r\n// delta\r\n.create-or-alter function \r\nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \r\ndelta(oldval: double, newval: double)\r\n{\r\n (newval - todouble(oldval))/oldval\r\n}\r\n\r\n// percentOfTotal\r\n// NOTE: Must be before percent() function\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercentOfTotal(t: (Count: long), tot: long)\r\n{\r\n let total = todouble(tot);\r\n t \r\n | extend Percent = round(Count / total * 100, 3) \r\n | order by Count desc\r\n}\r\n\r\n// percent\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercent(t: (Count: long))\r\n{\r\n let total = todouble(toscalar(t | summarize sum(Count)));\r\n percentOfTotal(t, total)\r\n}\r\n\r\n// plusminus\r\n.create-or-alter function \r\nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\r\nplusminus(val: string)\r\n{\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, val, strcat('+', val))\r\n}\r\n\r\n// updown\r\n.create-or-alter function \r\nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\r\nupdown(val: string)\r\n{\r\n // TODO: Handle 0\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\r\n}\r\n\r\n\r\n//===| String functions |===============================================================================================\r\n\r\n// percentstring\r\n// NOTE: Must be defined before deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\r\npercentstring(num: double, total: double = 1.0, places: int = 9)\r\n{\r\n let value = 1.0 * num / total * 100;\r\n strcat(case(\r\n places != 9, round(value, places),\r\n value < 10, round(value, 2),\r\n round(value, 1)\r\n ), '%')\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// arraystring\r\n.create-or-alter function \r\nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\r\narraystring(arr: dynamic)\r\n{\r\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\r\n tostring(arr)\r\n , @'^\\[\"', '')\r\n , @'\"\\]$', '')\r\n , @'^, ', '')\r\n , @', $', '')\r\n , @'^\\[]$', '')\r\n , '\",\"', ', ')\r\n}\r\n\r\n// deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\r\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\r\n{\r\n let d = delta(oldval, newval);\r\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\r\n}\r\n\r\n// diffstring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\r\ndiffstring(oldval: double, newval: double, places: int = 1)\r\n{\r\n plusminus(round(newval - oldval, places))\r\n}\r\n\r\n// numberstring\r\n.create-or-alter function \r\nwith (docstring = 'Convert a number to a string', folder = 'Common')\r\nnumberstring(num: double, abbrev: bool = true)\r\n{\r\n replace_regex(case(\r\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\r\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\r\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\r\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\r\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\r\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\r\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\r\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\r\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\r\n tostring(num)\r\n ), @'\\.0$', '')\r\n}\r\n\r\n\r\n//===| Other |==========================================================================================================\r\n\r\n// ifempty\r\n.create-or-alter function \r\nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\r\nifempty(val: dynamic, defaultVal: dynamic)\r\n{\r\n iff(isempty(val), defaultVal, val)\r\n}\r\n", + "$fxv#12": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / Open data functions\r\n// Wrap Ingestion database tables for easy access.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// PricingUnits\r\n.create-or-alter function\r\nwith (docstring = 'Gets pricing units from the FinOps toolkit PricingUnits open data.', folder = 'OpenData')\r\nPricingUnits()\r\n{\r\n database('Ingestion').PricingUnits\r\n}\r\n\r\n// Regions\r\n.create-or-alter function\r\nwith (docstring = 'Gets regions from the FinOps toolkit Regions open data.', folder = 'OpenData')\r\nRegion()\r\n{\r\n database('Ingestion').Regions\r\n}\r\n\r\n// ResourceTypes\r\n.create-or-alter function\r\nwith (docstring = 'Gets resource types from the FinOps toolkit ResourceTypes open data.', folder = 'OpenData')\r\nResourceType()\r\n{\r\n database('Ingestion').ResourceTypes\r\n}\r\n\r\n// Services\r\n.create-or-alter function\r\nwith (docstring = 'Gets services from the FinOps toolkit Services open data.', folder = 'OpenData')\r\nServices()\r\n{\r\n database('Ingestion').Services\r\n}\r\n", + "$fxv#13": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / FOCUS 1.0 functions\r\n// Used for reporting with backward compatibility.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// CommitmentDiscountUsage_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.0.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage_v1_0()\r\n{\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\r\n | union (\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n x_CommitmentDiscountCommittedCount = todecimal(x_CommitmentDiscountCommittedCount),\r\n x_CommitmentDiscountCommittedAmount = todecimal(x_CommitmentDiscountCommittedAmount),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio)\r\n )\r\n | project\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount,\r\n x_CommitmentDiscountCommittedAmount,\r\n x_CommitmentDiscountNormalizedGroup,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountQuantity,\r\n x_IngestionTime,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceModel,\r\n x_SkuOrderId,\r\n x_SkuSize,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Costs_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.0.', folder = 'Costs')\r\nCosts_v1_0()\r\n{\r\n database('Ingestion').Costs_final_v1_0\r\n | union (\r\n database('Ingestion').Costs_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n ContractedCost = todecimal(ContractedCost),\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n EffectiveCost = todecimal(EffectiveCost),\r\n ListCost = todecimal(ListCost),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\r\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\r\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\r\n // Rename columns\r\n | project-rename\r\n x_InvoiceId = InvoiceId,\r\n x_PricingCurrency = PricingCurrency,\r\n x_SkuMeterName = SkuMeter\r\n // Generate historical x_SkuDetails format from SkuPriceDetails\r\n | extend x_SkuDetails = iff(isnotempty(x_SkuDetails), x_SkuDetails, parse_json(replace_regex(tostring(SkuPriceDetails), @'([\\{,])\"x_', @'\\1\"')))\r\n )\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost,\r\n ContractedUnitPrice,\r\n EffectiveCost,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SkuId,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType,\r\n Tags,\r\n x_AccountId,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_ContractedCostInUsd,\r\n x_CostAllocationRuleName,\r\n x_CostCategories,\r\n x_CostCenter,\r\n x_Credits,\r\n x_CostType,\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount,\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InvoiceId,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_Operation,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingCurrency,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuIsCreditEligible,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_UsageType\r\n}\r\n\r\n\r\n// Prices_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices aligned to FOCUS 1.0.', folder = 'Prices')\r\nPrices_v1_0()\r\n{\r\n database('Ingestion').Prices_final_v1_0\r\n | union (\r\n database('Ingestion').Prices_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n x_BaseUnitPrice = todecimal(x_BaseUnitPrice),\r\n x_CommitmentDiscountNormalizedRatio = todecimal(x_CommitmentDiscountNormalizedRatio),\r\n x_ContractedUnitPriceDiscount = todecimal(x_ContractedUnitPriceDiscount),\r\n x_ContractedUnitPriceDiscountPercent = todecimal(x_ContractedUnitPriceDiscountPercent),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_EffectiveUnitPriceDiscount = todecimal(x_EffectiveUnitPriceDiscount),\r\n x_EffectiveUnitPriceDiscountPercent = todecimal(x_EffectiveUnitPriceDiscountPercent),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize),\r\n x_SkuIncludedQuantity = todecimal(x_SkuIncludedQuantity),\r\n x_SkuTier = todecimal(x_SkuTier),\r\n x_TotalUnitPriceDiscount = todecimal(x_TotalUnitPriceDiscount),\r\n x_TotalUnitPriceDiscountPercent = todecimal(x_TotalUnitPriceDiscountPercent) \r\n // Rename columns\r\n | project-rename\r\n x_PricingCurrency = PricingCurrency,\r\n x_SkuMeterName = SkuMeter\r\n )\r\n | project\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType,\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingUnit,\r\n SkuId,\r\n SkuPriceId,\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent,\r\n x_EffectivePeriodEnd,\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingCurrency,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_SkuDescription,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent\r\n}\r\n\r\n\r\n// Recommendations_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.0.', folder = 'Recommendations')\r\nRecommendations_v1_0()\r\n{\r\n database('Ingestion').Recommendations_final_v1_0\r\n | union (\r\n database('Ingestion').Recommendations_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n x_EffectiveCostAfter = todecimal(x_EffectiveCostAfter),\r\n x_EffectiveCostBefore = todecimal(x_EffectiveCostBefore),\r\n x_EffectiveCostSavings = todecimal(x_EffectiveCostSavings)\r\n )\r\n | project\r\n ProviderName,\r\n SubAccountId,\r\n x_IngestionTime,\r\n x_EffectiveCostAfter,\r\n x_EffectiveCostBefore,\r\n x_EffectiveCostSavings,\r\n x_RecommendationDate,\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Transactions_final_v1_0\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.0.', folder = 'Transactions')\r\nTransactions_v1_0()\r\n{\r\n database('Ingestion').Transactions_final_v1_0\r\n | union (\r\n database('Ingestion').Transactions_final_v1_2\r\n // Convert real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n x_MonetaryCommitment = todecimal(x_MonetaryCommitment),\r\n x_Overage = todecimal(x_Overage)\r\n // Rename columns\r\n | project-rename\r\n x_InvoiceId = InvoiceId\r\n )\r\n | project\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodStart,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n RegionId,\r\n RegionName,\r\n SubAccountId,\r\n SubAccountName,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_CostCenter,\r\n x_InvoiceId,\r\n x_InvoiceNumber,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_IngestionTime,\r\n x_MonetaryCommitment,\r\n x_Overage,\r\n x_PurchasingBillingAccountId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuSize,\r\n x_SkuTerm,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId,\r\n x_TransactionType\r\n}\r\n", + "$fxv#14": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / FOCUS 1.2 functions\r\n// Used for reporting with backward compatibility.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n// CommitmentDiscountUsage_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records aligned to FOCUS 1.2.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage_v1_2()\r\n{\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_2\r\n | union (\r\n database('Ingestion').CommitmentDiscountUsage_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n ConsumedQuantity = toreal(ConsumedQuantity),\r\n x_CommitmentDiscountCommittedCount = toreal(x_CommitmentDiscountCommittedCount),\r\n x_CommitmentDiscountCommittedAmount = toreal(x_CommitmentDiscountCommittedAmount),\r\n x_CommitmentDiscountNormalizedRatio = toreal(x_CommitmentDiscountNormalizedRatio)\r\n // Add new columns\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceSubcategory) on x_ResourceType\r\n | extend CommitmentDiscountQuantity = ConsumedQuantity * x_CommitmentDiscountNormalizedRatio\r\n | extend CommitmentDiscountUnit = case(\r\n x_CommitmentDiscountNormalizedRatio == 1, 'Hours',\r\n x_CommitmentDiscountNormalizedRatio > 1, 'Normalized Hours',\r\n ''\r\n )\r\n )\r\n | project\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount,\r\n x_CommitmentDiscountCommittedAmount,\r\n x_CommitmentDiscountNormalizedGroup,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_IngestionTime,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceModel,\r\n x_SkuOrderId,\r\n x_SkuSize,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Costs_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records aligned to FOCUS 1.2.', folder = 'Costs')\r\nCosts_v1_2()\r\n{\r\n database('Ingestion').Costs_final_v1_2\r\n | union (\r\n database('Ingestion').Costs_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n BilledCost = toreal(BilledCost),\r\n ConsumedQuantity = toreal(ConsumedQuantity),\r\n ContractedCost = toreal(ContractedCost),\r\n ContractedUnitPrice = toreal(ContractedUnitPrice),\r\n EffectiveCost = toreal(EffectiveCost),\r\n ListCost = toreal(ListCost),\r\n ListUnitPrice = toreal(ListUnitPrice),\r\n PricingQuantity = toreal(PricingQuantity),\r\n x_BilledCostInUsd = toreal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = toreal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = toreal(x_BillingExchangeRate),\r\n x_ContractedCostInUsd = toreal(x_ContractedCostInUsd),\r\n x_CurrencyConversionRate = toreal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = toreal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = toreal(x_ListCostInUsd),\r\n x_PricingBlockSize = toreal(x_PricingBlockSize)\r\n // Rename columns\r\n | project-rename\r\n InvoiceId = x_InvoiceId,\r\n PricingCurrency = x_PricingCurrency,\r\n SkuMeter = x_SkuMeterName\r\n // Add new columns\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceSubcategory, x_ServiceModel) on x_ResourceType\r\n | extend CapacityReservationId = tostring(x_SkuDetails.VMCapacityReservationId)\r\n | extend CapacityReservationStatus = case(\r\n isempty(CapacityReservationId), '',\r\n tolower(x_ResourceType) == 'microsoft.compute/capacityreservationgroups/capacityreservations', 'Unused',\r\n 'Used'\r\n )\r\n | extend x_CommitmentDiscountNormalizedRatio = case(\r\n // Not applicable\r\n isempty(CommitmentDiscountStatus), real(null),\r\n // Parse from SKU details if not specified explicitly\r\n toreal(coalesce(x_SkuDetails.RINormalizationRatio, dynamic(1)))\r\n )\r\n | extend CommitmentDiscountQuantity = case(\r\n isempty(CommitmentDiscountStatus), real(null),\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost / coalesce(x_BillingExchangeRate, real(1)),\r\n CommitmentDiscountCategory == 'Usage' and isnotempty(x_CommitmentDiscountNormalizedRatio), PricingQuantity / coalesce(x_PricingBlockSize, real(1)) * x_CommitmentDiscountNormalizedRatio,\r\n real(null)\r\n )\r\n | extend CommitmentDiscountUnit = case(\r\n isempty(CommitmentDiscountQuantity), '',\r\n CommitmentDiscountCategory == 'Spend', PricingCurrency,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio == real(1), ConsumedUnit,\r\n CommitmentDiscountCategory == 'Usage' and x_CommitmentDiscountNormalizedRatio > real(1), strcat('Normalized ', ConsumedUnit),\r\n ''\r\n )\r\n | extend x_AmortizationClass = case(\r\n ChargeCategory == 'Purchase' and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeCategory == 'Usage' and isnotempty(CommitmentDiscountId) and isnotempty(CommitmentDiscountStatus), 'Amortized Charge',\r\n ''\r\n )\r\n // Hubs add-ons\r\n | extend x_CommitmentDiscountUtilizationPotential = case(\r\n ChargeCategory == 'Purchase', real(0),\r\n ProviderName == 'Microsoft' and isnotempty(CommitmentDiscountCategory), EffectiveCost,\r\n CommitmentDiscountCategory == 'Usage', ConsumedQuantity,\r\n CommitmentDiscountCategory == 'Spend', EffectiveCost,\r\n real(0)\r\n )\r\n | extend x_CommitmentDiscountUtilizationAmount = iff(CommitmentDiscountStatus == 'Used', x_CommitmentDiscountUtilizationPotential, real(0))\r\n | extend x_SkuCoreCount = toint(coalesce(x_SkuDetails.VCPUs, x_SkuDetails.VCores, x_SkuDetails.vCores))\r\n | extend x_SkuInstanceType = tostring(coalesce(x_SkuDetails.ServiceType, x_SkuDetails.ServerSku))\r\n | extend x_SkuOperatingSystem = case(\r\n x_SkuDetails.ImageType == 'Canonical', 'Linux',\r\n x_SkuDetails.ImageType == 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory endswith ' Series Windows', 'Windows Server',\r\n x_SkuDetails.ImageType\r\n )\r\n | extend x_ConsumedCoreHours = iff(ConsumedUnit == 'Hours' and isnotempty(x_SkuCoreCount), x_SkuCoreCount * ConsumedQuantity, real(null))\r\n | extend tmp_SqlAhb = tolower(x_SkuDetails.AHB)\r\n | extend x_SkuLicenseType = case(\r\n x_SkuDetails.ImageType contains 'Windows Server BYOL', 'Windows Server',\r\n x_SkuMeterSubcategory == 'SQL Server Azure Hybrid Benefit', 'SQL Server',\r\n ''\r\n )\r\n | extend x_SkuLicenseStatus = case(\r\n isnotempty(x_SkuLicenseType) or tmp_SqlAhb == 'true' or (x_SkuMeterSubcategory contains 'Azure Hybrid Benefit'), 'Enabled',\r\n (x_SkuMeterSubcategory contains 'Windows') or tmp_SqlAhb == 'false', 'Not enabled',\r\n ''\r\n )\r\n | extend x_SkuLicenseQuantity = case(\r\n isempty(x_SkuCoreCount), int(null),\r\n x_SkuCoreCount <= 8, int(8),\r\n x_SkuCoreCount > 8, x_SkuCoreCount,\r\n int(null)\r\n )\r\n | extend x_SkuLicenseUnit = iff(isnotempty(x_SkuLicenseQuantity), 'Cores', '')\r\n | extend x_CommitmentDiscountSavings = iff(ContractedCost < EffectiveCost, real(0), ContractedCost - EffectiveCost)\r\n | extend x_NegotiatedDiscountSavings = iff(ListCost < ContractedCost, real(0), ListCost - ContractedCost)\r\n | extend x_TotalSavings = iff(ListCost < EffectiveCost, real(0), ListCost - EffectiveCost)\r\n | extend x_CommitmentDiscountPercent = iff(ContractedUnitPrice == 0, real(0), (ContractedUnitPrice - x_EffectiveUnitPrice) / ContractedUnitPrice)\r\n | extend x_NegotiatedDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - ContractedUnitPrice) / ListUnitPrice)\r\n | extend x_TotalDiscountPercent = iff(ListUnitPrice == 0, real(0), (ListUnitPrice - x_EffectiveUnitPrice) / ListUnitPrice)\r\n // SkuPriceDetails conversion -- Must be after hubs add-ons\r\n | extend SkuPriceDetails = parse_json(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(tostring(x_SkuDetails)\r\n // Prefix all keys with x_ first to avoid double-prefixing\r\n , @'([\\{,])\"', @'\\1\"x_')\r\n // CoreCount for number of CPUs/vCPUs/cores/vCores\r\n , @'\"x_(VCPUs|VCores|vCores)\":', @'\"CoreCount\":')\r\n // TODO: DiskMaxIops for disk I/O operations per second (IOPS)\r\n // TODO: DiskSpace for disk size in GiB\r\n // TODO: DiskType for the kind of disk (e.g., SSD, HDD, NVMe)\r\n // TODO: GpuCount for the number of GPUs\r\n // InstanceType for the resource size/SKU (e.g., ArmSkuName)\r\n , @'\"x_(ServerSku|ServiceType)\":', @'\"InstanceType\":')\r\n // TODO: InstanceSeries for the size family/series\r\n // TODO: MemorySize for the RAM in GiB\r\n // TODO: NetworkMaxIops for network I/O operations per second (IOPS)\r\n // TODO: NetworkMaxThroughput for network max throughput for data transfer in Mbps\r\n // OperatingSystem for the OS name\r\n , @'(\"x_ImageType\":\"Canonical\")', @'\\1,\"OperatingSystem\":\"Linux\"')\r\n , @'(\"x_ImageType\":\"Windows Server( BYOL)?\")', @'\\1,\"OperatingSystem\":\"Windows Server\"')\r\n , @'(\"x_ImageType\":(\"[^\"]+\"))', @'\\1,\"OperatingSystem\":\\2')\r\n // TODO: Redundancy for the level of redundancy (e.g., Local, Zonal, Global)\r\n // TODO: StorageClass for the tier of storage (e.g., Hot, Archive, Nearline)\r\n )\r\n | extend SkuPriceDetails = iff(isempty(SkuPriceDetails.OperatingSystem) and isnotempty(x_SkuOperatingSystem),\r\n parse_json(replace_string(tostring(SkuPriceDetails), '}', strcat(@',\"OperatingSystem\":\"', x_SkuOperatingSystem, '\"}'))),\r\n SkuPriceDetails)\r\n )\r\n | extend SkuPriceDetails = iff(isnotempty(SkuPriceDetails), SkuPriceDetails, parse_json(replace_regex(tostring(x_SkuDetails), @'([\\{,])\"', @'\\1\"x_')))\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n CapacityReservationId,\r\n CapacityReservationStatus,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId,\r\n CommitmentDiscountName,\r\n CommitmentDiscountQuantity,\r\n CommitmentDiscountStatus,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost,\r\n ContractedUnitPrice,\r\n EffectiveCost,\r\n InvoiceId,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n PublisherName,\r\n RegionId,\r\n RegionName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory,\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceDetails,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType,\r\n Tags,\r\n x_AccountId,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_AmortizationClass,\r\n x_BilledCostInUsd,\r\n x_BilledUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingAccountName,\r\n x_BillingExchangeRate,\r\n x_BillingExchangeRateDate,\r\n x_BillingItemCode,\r\n x_BillingItemName,\r\n x_BillingProfileId,\r\n x_BillingProfileName,\r\n x_ChargeId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountPercent,\r\n x_CommitmentDiscountSavings,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_CommitmentDiscountUtilizationAmount,\r\n x_CommitmentDiscountUtilizationPotential,\r\n x_CommodityCode,\r\n x_CommodityName,\r\n x_ComponentName,\r\n x_ComponentType,\r\n x_ConsumedCoreHours,\r\n x_ContractedCostInUsd,\r\n x_CostAllocationRuleName,\r\n x_CostCategories,\r\n x_CostCenter,\r\n x_CostType,\r\n x_Credits,\r\n x_CurrencyConversionRate,\r\n x_CustomerId,\r\n x_CustomerName,\r\n x_Discount,\r\n x_EffectiveCostInUsd,\r\n x_EffectiveUnitPrice,\r\n x_ExportTime,\r\n x_IngestionTime,\r\n x_InstanceID,\r\n x_InvoiceIssuerId,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_ListCostInUsd,\r\n x_Location,\r\n x_NegotiatedDiscountPercent,\r\n x_NegotiatedDiscountSavings,\r\n x_Operation,\r\n x_OwnerAccountID,\r\n x_PartnerCreditApplied,\r\n x_PartnerCreditRate,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_Project,\r\n x_PublisherCategory,\r\n x_PublisherId,\r\n x_ResellerId,\r\n x_ResellerName,\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n x_ServiceCode,\r\n x_ServiceId,\r\n x_ServiceModel,\r\n x_ServicePeriodEnd,\r\n x_ServicePeriodStart,\r\n x_SkuCoreCount,\r\n x_SkuDescription,\r\n x_SkuDetails,\r\n x_SkuInstanceType,\r\n x_SkuIsCreditEligible,\r\n x_SkuLicenseQuantity,\r\n x_SkuLicenseStatus,\r\n x_SkuLicenseType,\r\n x_SkuLicenseUnit,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuOfferId,\r\n x_SkuOperatingSystem,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuPartNumber,\r\n x_SkuPlanName,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceChanges,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceValues,\r\n x_SourceVersion,\r\n x_SubproductName,\r\n x_TotalDiscountPercent,\r\n x_TotalSavings,\r\n x_UsageType\r\n}\r\n\r\n\r\n// Prices_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices aligned to FOCUS 1.2.', folder = 'Prices')\r\nPrices_v1_2()\r\n{\r\n database('Ingestion').Prices_final_v1_2\r\n | union (\r\n database('Ingestion').Prices_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n ContractedUnitPrice = toreal(ContractedUnitPrice),\r\n ListUnitPrice = toreal(ListUnitPrice),\r\n x_BaseUnitPrice = toreal(x_BaseUnitPrice),\r\n x_ContractedUnitPriceDiscount = toreal(x_ContractedUnitPriceDiscount),\r\n x_ContractedUnitPriceDiscountPercent = toreal(x_ContractedUnitPriceDiscountPercent),\r\n x_EffectiveUnitPrice = toreal(x_EffectiveUnitPrice),\r\n x_EffectiveUnitPriceDiscount = toreal(x_EffectiveUnitPriceDiscount),\r\n x_EffectiveUnitPriceDiscountPercent = toreal(x_EffectiveUnitPriceDiscountPercent),\r\n x_PricingBlockSize = toreal(x_PricingBlockSize),\r\n x_SkuIncludedQuantity = toreal(x_SkuIncludedQuantity),\r\n x_SkuTier = toreal(x_SkuTier),\r\n x_TotalUnitPriceDiscount = toreal(x_TotalUnitPriceDiscount),\r\n x_TotalUnitPriceDiscountPercent = toreal(x_TotalUnitPriceDiscountPercent) \r\n // Rename columns\r\n | project-rename\r\n PricingCurrency = x_PricingCurrency,\r\n SkuMeter = x_SkuMeterName\r\n )\r\n | project\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n ChargeCategory,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountType,\r\n CommitmentDiscountUnit,\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory,\r\n PricingCurrency,\r\n PricingUnit,\r\n SkuId,\r\n SkuMeter,\r\n SkuPriceId,\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement,\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountNormalizedRatio,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent,\r\n x_EffectivePeriodEnd,\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingSubcategory,\r\n x_PricingUnitDescription,\r\n x_SkuDescription,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent\r\n}\r\n\r\n\r\n// Recommendations_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations aligned to FOCUS 1.2.', folder = 'Recommendations')\r\nRecommendations_v1_2()\r\n{\r\n database('Ingestion').Recommendations_final_v1_2\r\n | union (\r\n database('Ingestion').Recommendations_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n x_EffectiveCostAfter = toreal(x_EffectiveCostAfter),\r\n x_EffectiveCostBefore = toreal(x_EffectiveCostBefore),\r\n x_EffectiveCostSavings = toreal(x_EffectiveCostSavings)\r\n )\r\n | project\r\n ProviderName,\r\n SubAccountId,\r\n x_IngestionTime,\r\n x_EffectiveCostAfter,\r\n x_EffectiveCostBefore,\r\n x_EffectiveCostSavings,\r\n x_RecommendationDate,\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n\r\n// Transactions_final_v1_2\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions aligned to FOCUS 1.2.', folder = 'Transactions')\r\nTransactions_v1_2()\r\n{\r\n database('Ingestion').Transactions_final_v1_2\r\n | union (\r\n database('Ingestion').Transactions_final_v1_0\r\n // Convert decimal to real\r\n | extend\r\n BilledCost = toreal(BilledCost),\r\n PricingQuantity = toreal(PricingQuantity),\r\n x_MonetaryCommitment = toreal(x_MonetaryCommitment),\r\n x_Overage = toreal(x_Overage)\r\n // Rename columns\r\n | project-rename\r\n InvoiceId = x_InvoiceId\r\n )\r\n | project\r\n BilledCost,\r\n BillingAccountId,\r\n BillingAccountName,\r\n BillingCurrency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory,\r\n ChargeClass,\r\n ChargeDescription,\r\n ChargeFrequency,\r\n ChargePeriodStart,\r\n InvoiceId,\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n RegionId,\r\n RegionName,\r\n SubAccountId,\r\n SubAccountName,\r\n x_AccountName,\r\n x_AccountOwnerId,\r\n x_CostCenter,\r\n x_InvoiceNumber,\r\n x_InvoiceSectionId,\r\n x_InvoiceSectionName,\r\n x_IngestionTime,\r\n x_MonetaryCommitment,\r\n x_Overage,\r\n x_PurchasingBillingAccountId,\r\n x_SkuOrderId,\r\n x_SkuOrderName,\r\n x_SkuSize,\r\n x_SkuTerm,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId,\r\n x_TransactionType\r\n}\r\n\r\n\r\n//======================================================================================================================\r\n// Latest FOCUS version\r\n//======================================================================================================================\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage()\r\n{\r\n CommitmentDiscountUsage_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\r\nCosts()\r\n{\r\n Costs_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\r\nPrices()\r\n{\r\n Prices_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\r\nRecommendations()\r\n{\r\n Recommendations_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\r\nTransactions()\r\n{\r\n Transactions_v1_2()\r\n}\r\n", + "$fxv#15": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Hub database / Latest FOCUS version functions\r\n// Used for ad hoc queries.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all commitment discount usage records with the latest supported version of the FOCUS schema.', folder = 'CommitmentDiscountUsage')\r\nCommitmentDiscountUsage()\r\n{\r\n CommitmentDiscountUsage_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all cost and usage records with the latest supported version of the FOCUS schema.', folder = 'Costs')\r\nCosts()\r\n{\r\n Costs_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all prices with the latest supported version of the FOCUS schema.', folder = 'Prices')\r\nPrices()\r\n{\r\n Prices_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all recommendations with the latest supported version of the FOCUS schema.', folder = 'Recommendations')\r\nRecommendations()\r\n{\r\n Recommendations_v1_2()\r\n}\r\n\r\n\r\n.create-or-alter function\r\nwith (docstring = 'Gets all transactions with the latest supported version of the FOCUS schema.', folder = 'Transactions')\r\nTransactions()\r\n{\r\n Transactions_v1_2()\r\n}\r\n", + "$fxv#2": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_3(id: string) {\r\n dynamic({\r\n \"microsoft.hybridnetwork/vendors\": { \"SingularDisplayName\": \"Azure Network Function Manager ? vendor\" }\r\n ,\"microsoft.hybridonboarding/extensionmanagers\": { \"SingularDisplayName\": \"Microsoft.HybridOnboarding extension manager\" }\r\n ,\"microsoft.impact/connectors\": { \"SingularDisplayName\": \"Impact Reporting Connector\" }\r\n ,\"microsoft.impact/impactcategories\": { \"SingularDisplayName\": \"Microsoft.Impact impact category\" }\r\n ,\"microsoft.impact/topologyimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact topology impact\" }\r\n ,\"microsoft.impact/workloadimpacts\": { \"SingularDisplayName\": \"Microsoft.Impact workload impact\" }\r\n ,\"microsoft.impact/workloadimpacts/insights\": { \"SingularDisplayName\": \"Microsoft.Impact workload impacts insight\" }\r\n ,\"microsoft.importexport/jobs\": { \"SingularDisplayName\": \"Microsoft.ImportExport job\" }\r\n ,\"microsoft.insights/actiongroups\": { \"SingularDisplayName\": \"Action group\" }\r\n ,\"microsoft.insights/activitylogalerts\": { \"SingularDisplayName\": \"Activity log alert rule\" }\r\n ,\"microsoft.insights/alertrules\": { \"SingularDisplayName\": \"Microsoft.Insights alertrule\" }\r\n ,\"microsoft.insights/alertrules/incidents\": { \"SingularDisplayName\": \"Microsoft.insights alertrules incident\" }\r\n ,\"microsoft.insights/autoscalesettings\": { \"SingularDisplayName\": \"Microsoft.Insights autoscalesetting\" }\r\n ,\"microsoft.insights/components\": { \"SingularDisplayName\": \"Application Insights app\" }\r\n ,\"microsoft.insights/datacollectionendpoints\": { \"SingularDisplayName\": \"Data collection endpoint\" }\r\n ,\"microsoft.insights/datacollectionruleassociations\": { \"SingularDisplayName\": \"Microsoft.Insights data collection rule association\" }\r\n ,\"microsoft.insights/datacollectionrules\": { \"SingularDisplayName\": \"Data collection rule\" }\r\n ,\"microsoft.insights/datacollectionrulesresources\": { \"SingularDisplayName\": \"Data collection rule associated resource\" }\r\n ,\"microsoft.insights/diagnosticsettings\": { \"SingularDisplayName\": \"Diagnostic settings\" }\r\n ,\"microsoft.insights/diagnosticsettingscategories\": { \"SingularDisplayName\": \"Microsoft.Insights diagnostic settings category\" }\r\n ,\"microsoft.insights/guestdiagnosticsettings\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic setting\" }\r\n ,\"microsoft.insights/guestdiagnosticsettingsassociation\": { \"SingularDisplayName\": \"Microsoft.insights guest diagnostic settings association\" }\r\n ,\"microsoft.insights/logprofiles\": { \"SingularDisplayName\": \"Microsoft.Insights logprofile\" }\r\n ,\"microsoft.insights/metricalerts\": { \"SingularDisplayName\": \"Metric alert rule\" }\r\n ,\"microsoft.insights/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights notification statu\" }\r\n ,\"microsoft.insights/privatelinkscopeoperationstatuses\": { \"SingularDisplayName\": \"Microsoft.insights private link scope operation statuse\" }\r\n ,\"microsoft.insights/privatelinkscopes\": { \"SingularDisplayName\": \"Azure Monitor Private Link Scope\" }\r\n ,\"microsoft.insights/scheduledqueryrules\": { \"SingularDisplayName\": \"Log search alert rule\" }\r\n ,\"microsoft.insights/tenantactiongroups\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action group\" }\r\n ,\"microsoft.insights/tenantactiongroups/notificationstatus\": { \"SingularDisplayName\": \"Microsoft.Insights tenant action groups notification statu\" }\r\n ,\"microsoft.insights/vminsightsonboardingstatuses\": { \"SingularDisplayName\": \"Microsoft.Insights VM insights onboarding statuse\" }\r\n ,\"microsoft.insights/webtests\": { \"SingularDisplayName\": \"Application Insights availability test\" }\r\n ,\"microsoft.insights/workbooks\": { \"SingularDisplayName\": \"Azure Workbook\" }\r\n ,\"microsoft.insights/workbooktemplates\": { \"SingularDisplayName\": \"Azure Workbook Template\" }\r\n ,\"microsoft.integrationspaces/spaces\": { \"SingularDisplayName\": \"Integration Environment\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twin\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/assets\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins asset\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/executionplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins execution plan\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/testplans\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test plan\" }\r\n ,\"microsoft.intelligentitdigitaltwin/digitaltwins/tests\": { \"SingularDisplayName\": \"Microsoft.IntelligentITDigitalTwin digital twins test\" }\r\n ,\"microsoft.inventory/subscriptioninternalproperties\": { \"SingularDisplayName\": \"Microsoft.Inventory subscription internal property\" }\r\n ,\"microsoft.iotcentral/iotapps\": { \"SingularDisplayName\": \"IoT Central Application\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces\": { \"SingularDisplayName\": \"Firmware analysis workspace\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmware\" }\r\n ,\"microsoft.iotfirmwaredefense/workspaces/firmwares/summaries\": { \"SingularDisplayName\": \"Microsoft.IoTFirmwareDefense workspaces firmwares summary\" }\r\n ,\"microsoft.iotoperations/instances\": { \"SingularDisplayName\": \"Azure IoT Operations\" }\r\n ,\"microsoft.iotoperations/instances/brokers\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances broker\" }\r\n ,\"microsoft.iotoperations/instances/brokers/authentications\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authentication\" }\r\n ,\"microsoft.iotoperations/instances/brokers/authorizations\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers authorization\" }\r\n ,\"microsoft.iotoperations/instances/brokers/listeners\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances brokers listener\" }\r\n ,\"microsoft.iotoperations/instances/dataflowendpoints\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow endpoint\" }\r\n ,\"microsoft.iotoperations/instances/dataflowprofiles\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profile\" }\r\n ,\"microsoft.iotoperations/instances/dataflowprofiles/dataflows\": { \"SingularDisplayName\": \"Microsoft.IoTOperations instances dataflow profiles dataflow\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instance\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances/datasets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances dataset\" }\r\n ,\"microsoft.iotoperationsdataprocessor/instances/pipelines\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsDataProcessor instances pipeline\" }\r\n ,\"microsoft.iotoperationsmq/mq\": { \"SingularDisplayName\": \"IoT Operations Ops MQ\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/authentication\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authentication\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/authorization\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker authorization\" }\r\n ,\"microsoft.iotoperationsmq/mq/broker/listener\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq broker listener\" }\r\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/datalakeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq data lake connector topic map\" }\r\n ,\"microsoft.iotoperationsmq/mq/diagnosticservice\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq diagnostic service\" }\r\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/kafkaconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq kafka connector topic map\" }\r\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector\" }\r\n ,\"microsoft.iotoperationsmq/mq/mqttbridgeconnector/topicmap\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsMQ mq mqtt bridge connector topic map\" }\r\n ,\"microsoft.iotoperationsorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator instance\" }\r\n ,\"microsoft.iotoperationsorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator solution\" }\r\n ,\"microsoft.iotoperationsorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.IoTOperationsOrchestrator target\" }\r\n ,\"microsoft.iotsecurity/alerttypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity alert type\" }\r\n ,\"microsoft.iotsecurity/defendersettings\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity defender setting\" }\r\n ,\"microsoft.iotsecurity/onpremisesensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity on premise sensor\" }\r\n ,\"microsoft.iotsecurity/recommendationtypes\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity recommendation type\" }\r\n ,\"microsoft.iotsecurity/sensors\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity sensor\" }\r\n ,\"microsoft.iotsecurity/sites\": { \"SingularDisplayName\": \"Microsoft.IoTSecurity site\" }\r\n ,\"microsoft.keyvault/managedhsms\": { \"SingularDisplayName\": \"Azure Key Vault Managed HSM\" }\r\n ,\"microsoft.keyvault/vaults\": { \"SingularDisplayName\": \"Key vault\" }\r\n ,\"microsoft.kubernetes/connectedclusters\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc extension\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"GitOps configuration\" }\r\n ,\"microsoft.kubernetes/connectedclusters/microsoft.kubernetesconfiguration/namespaces\": { \"SingularDisplayName\": \"Kubernetes - Azure Arc namespace\" }\r\n ,\"microsoft.kubernetesconfiguration/extensions\": { \"SingularDisplayName\": \"Kubernetes service extension\" }\r\n ,\"microsoft.kubernetesconfiguration/extensiontypes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension type\" }\r\n ,\"microsoft.kubernetesconfiguration/extensiontypes/versions\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration extension types version\" }\r\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configuration\" }\r\n ,\"microsoft.kubernetesconfiguration/fluxconfigurations/operations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration flux configurations operation\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scope\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private endpoint connection\" }\r\n ,\"microsoft.kubernetesconfiguration/privatelinkscopes/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration private link scopes private link resource\" }\r\n ,\"microsoft.kubernetesconfiguration/sourcecontrolconfigurations\": { \"SingularDisplayName\": \"Microsoft.KubernetesConfiguration source control configuration\" }\r\n ,\"microsoft.kubernetesruntime/bgppeers\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime bgp peer\" }\r\n ,\"microsoft.kubernetesruntime/loadbalancers\": { \"SingularDisplayName\": \"Arc Load Balancer\" }\r\n ,\"microsoft.kubernetesruntime/services\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime service\" }\r\n ,\"microsoft.kubernetesruntime/storageclasses\": { \"SingularDisplayName\": \"Microsoft.KubernetesRuntime storage class\" }\r\n ,\"microsoft.kusto/clusters\": { \"SingularDisplayName\": \"Azure Data Explorer Cluster\" }\r\n ,\"microsoft.kusto/clusters/databases\": { \"SingularDisplayName\": \"Azure Data Explorer Database\" }\r\n ,\"microsoft.labservices/labaccounts\": { \"SingularDisplayName\": \"Lab account\" }\r\n ,\"microsoft.labservices/labaccounts/labs\": { \"SingularDisplayName\": \"Lab\" }\r\n ,\"microsoft.labservices/labplans\": { \"SingularDisplayName\": \"Lab plan\" }\r\n ,\"microsoft.labservices/labs\": { \"SingularDisplayName\": \"Lab\" }\r\n ,\"microsoft.liftrpilot/organizations\": { \"SingularDisplayName\": \"Azure Pilot\" }\r\n ,\"microsoft.loadtestservice/loadtestmappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test mapping\" }\r\n ,\"microsoft.loadtestservice/loadtestprofilemappings\": { \"SingularDisplayName\": \"Microsoft.LoadTestService load test profile mapping\" }\r\n ,\"microsoft.loadtestservice/loadtests\": { \"SingularDisplayName\": \"Azure Load Testing\" }\r\n ,\"microsoft.loadtestservice/playwrightworkspaces\": { \"SingularDisplayName\": \"Playwright Workspace\" }\r\n ,\"microsoft.logic/businessprocesses\": { \"SingularDisplayName\": \"Business Process\" }\r\n ,\"microsoft.logic/integrationaccounts\": { \"SingularDisplayName\": \"Logic app integration account\" }\r\n ,\"microsoft.logic/integrationserviceenvironments\": { \"SingularDisplayName\": \"Integration Service Environment\" }\r\n ,\"microsoft.logic/integrationserviceenvironments/health\": { \"SingularDisplayName\": \"Microsoft.Logic integration service environments health\" }\r\n ,\"microsoft.logic/integrationserviceenvironments/managedapis\": { \"SingularDisplayName\": \"Managed Connector\" }\r\n ,\"microsoft.logic/templates\": { \"SingularDisplayName\": \"Logic Apps Template\" }\r\n ,\"microsoft.logic/workflows\": { \"SingularDisplayName\": \"Logic app\" }\r\n ,\"microsoft.logz/monitors\": { \"SingularDisplayName\": \"Logz.io\" }\r\n ,\"microsoft.logz/monitors/accounts\": { \"SingularDisplayName\": \"Logz sub account\" }\r\n ,\"microsoft.m365/m365resources\": { \"SingularDisplayName\": \"Microsoft.M365 m365 resource\" }\r\n ,\"microsoft.m365consumptionservices/services\": { \"SingularDisplayName\": \"Microsoft.M365ConsumptionServices service\" }\r\n ,\"microsoft.machinelearning/commitmentplans\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plan\" }\r\n ,\"microsoft.machinelearning/commitmentplans/commitmentassociations\": { \"SingularDisplayName\": \"Microsoft.MachineLearning commitment plans commitment association\" }\r\n ,\"microsoft.machinelearning/webservices\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) web service\" }\r\n ,\"microsoft.machinelearning/workspaces\": { \"SingularDisplayName\": \"Machine Learning Studio (classic) workspace\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation account\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspace\" }\r\n ,\"microsoft.machinelearningexperimentation/accounts/workspaces/projects\": { \"SingularDisplayName\": \"Microsoft.MachineLearningExperimentation accounts workspaces project\" }\r\n ,\"microsoft.machinelearningservices/aistudio\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.machinelearningservices/aistudiocreate\": { \"SingularDisplayName\": \"Azure AI Foundry\" }\r\n ,\"microsoft.machinelearningservices/registries\": { \"SingularDisplayName\": \"Azure Machine Learning registry\" }\r\n ,\"microsoft.machinelearningservices/workspaces\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\r\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints\": { \"SingularDisplayName\": \"Machine learning online endpoint\" }\r\n ,\"microsoft.machinelearningservices/workspaces/onlineendpoints/deployments\": { \"SingularDisplayName\": \"Machine learning online deployment\" }\r\n ,\"microsoft.machinelearningservices/workspacescreate\": { \"SingularDisplayName\": \"Azure Machine Learning workspace\" }\r\n ,\"microsoft.maintenance/configurationassignments\": { \"SingularDisplayName\": \"Microsoft.Maintenance configuration assignment\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurations\": { \"SingularDisplayName\": \"Maintenance Configuration\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurationsaumbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\r\n ,\"microsoft.maintenance/maintenanceconfigurationsbladeresource\": { \"SingularDisplayName\": \"Maintenance configuration\" }\r\n ,\"microsoft.maintenance/publicmaintenanceconfigurations\": { \"SingularDisplayName\": \"Microsoft.Maintenance public maintenance configuration\" }\r\n ,\"microsoft.managedidentity/identities\": { \"SingularDisplayName\": \"Microsoft.ManagedIdentity identity\" }\r\n ,\"microsoft.managedidentity/userassignedidentities\": { \"SingularDisplayName\": \"Managed Identity\" }\r\n ,\"microsoft.managednetwork/managednetworks\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed network\" }\r\n ,\"microsoft.managednetwork/managednetworks/managednetworkgroups\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network group\" }\r\n ,\"microsoft.managednetwork/managednetworks/managednetworkpeeringpolicies\": { \"SingularDisplayName\": \"Microsoft.ManagedNetwork managed networks managed network peering policy\" }\r\n ,\"microsoft.managednetworkfabric/accesscontrollists\": { \"SingularDisplayName\": \"Access Control List (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/internetgatewayrules\": { \"SingularDisplayName\": \"Internet Gateway Rule (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/internetgateways\": { \"SingularDisplayName\": \"Internet Gateway (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipcommunities\": { \"SingularDisplayName\": \"IP Community (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipextendedcommunities\": { \"SingularDisplayName\": \"IP Extended Community (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/ipprefixes\": { \"SingularDisplayName\": \"IP Prefix (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l2isolationdomains\": { \"SingularDisplayName\": \"Layer 2 Isolation Domain (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains\": { \"SingularDisplayName\": \"Layer 3 Isolation Domain (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains/externalnetworks\": { \"SingularDisplayName\": \"External Network (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/l3isolationdomains/internalnetworks\": { \"SingularDisplayName\": \"Internal Network (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/neighborgroups\": { \"SingularDisplayName\": \"Neighbor Group (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkdevices\": { \"SingularDisplayName\": \"Network Device (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkdevices/networkinterfaces\": { \"SingularDisplayName\": \"Network Interface (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabriccontrollers\": { \"SingularDisplayName\": \"Network Fabric Controller (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabrics\": { \"SingularDisplayName\": \"Network Fabric (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabrics/networktonetworkinterconnects\": { \"SingularDisplayName\": \"Network to Network Interconnect (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkfabricskus\": { \"SingularDisplayName\": \"Network Fabric SKU (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkmonitors\": { \"SingularDisplayName\": \"Microsoft.ManagedNetworkFabric network monitor\" }\r\n ,\"microsoft.managednetworkfabric/networkpacketbrokers\": { \"SingularDisplayName\": \"Network Packet Broker (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networkracks\": { \"SingularDisplayName\": \"Network Rack (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networktaprules\": { \"SingularDisplayName\": \"Network Tap Rule (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/networktaps\": { \"SingularDisplayName\": \"Network Tap (Operator Nexus)\" }\r\n ,\"microsoft.managednetworkfabric/routepolicies\": { \"SingularDisplayName\": \"Route Policy (Operator Nexus)\" }\r\n ,\"microsoft.managedservices/marketplaceregistrationdefinitions\": { \"SingularDisplayName\": \"Microsoft.ManagedServices marketplace registration definition\" }\r\n ,\"microsoft.managedservices/registrationassignments\": { \"SingularDisplayName\": \"Microsoft.ManagedServices registration assignment\" }\r\n ,\"microsoft.managedservices/registrationdefinitions\": { \"SingularDisplayName\": \"Azure Lighthouse\" }\r\n ,\"microsoft.management/managementgroups\": { \"SingularDisplayName\": \"Microsoft.Management management group\" }\r\n ,\"microsoft.management/managementgroups/microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\r\n ,\"microsoft.management/managementgroups/providers/privatelinkassociations\": { \"SingularDisplayName\": \"Application Gateway\" }\r\n ,\"microsoft.management/managementgroups/providers/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\r\n ,\"microsoft.management/managementgroups/settings\": { \"SingularDisplayName\": \"Microsoft.Management management groups setting\" }\r\n ,\"microsoft.management/managementgroups/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Management management groups subscription\" }\r\n ,\"microsoft.management/servicegroups\": { \"SingularDisplayName\": \"Service group\" }\r\n ,\"microsoft.managementpartner/partners\": { \"SingularDisplayName\": \"Microsoft.ManagementPartner partner\" }\r\n ,\"microsoft.manufacturingplatform/manufacturingdataservices\": { \"SingularDisplayName\": \"Factory Operations Agent in Azure AI Foundry\" }\r\n ,\"microsoft.maps/accounts\": { \"SingularDisplayName\": \"Azure Maps Account\" }\r\n ,\"microsoft.maps/accounts/creators\": { \"SingularDisplayName\": \"Azure Maps Creator Resource\" }\r\n ,\"microsoft.marketplace/privatestores\": { \"SingularDisplayName\": \"Microsoft.Marketplace private store\" }\r\n ,\"microsoft.marketplace/privatestores/adminrequestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores admin request approval\" }\r\n ,\"microsoft.marketplace/privatestores/collections\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collection\" }\r\n ,\"microsoft.marketplace/privatestores/collections/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores collections offer\" }\r\n ,\"microsoft.marketplace/privatestores/offers\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores offer\" }\r\n ,\"microsoft.marketplace/privatestores/requestapprovals\": { \"SingularDisplayName\": \"Microsoft.Marketplace private stores request approval\" }\r\n ,\"microsoft.media/mediaservices\": { \"SingularDisplayName\": \"Media service\" }\r\n ,\"microsoft.media/mediaservices/accountfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services account filter\" }\r\n ,\"microsoft.media/mediaservices/assets\": { \"SingularDisplayName\": \"Microsoft.Media media services asset\" }\r\n ,\"microsoft.media/mediaservices/assets/assetfilters\": { \"SingularDisplayName\": \"Microsoft.Media media services assets asset filter\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks\": { \"SingularDisplayName\": \"Microsoft.Media media services assets track\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks/operationresults\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation result\" }\r\n ,\"microsoft.media/mediaservices/assets/tracks/operationstatuses\": { \"SingularDisplayName\": \"Microsoft.Media media services assets tracks operation statuse\" }\r\n ,\"microsoft.media/mediaservices/contentkeypolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services content key policy\" }\r\n ,\"microsoft.media/mediaservices/liveevents\": { \"SingularDisplayName\": \"Live event\" }\r\n ,\"microsoft.media/mediaservices/liveevents/liveoutputs\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices live events live output\" }\r\n ,\"microsoft.media/mediaservices/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private endpoint connection\" }\r\n ,\"microsoft.media/mediaservices/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Media mediaservices private link resource\" }\r\n ,\"microsoft.media/mediaservices/streamingendpoints\": { \"SingularDisplayName\": \"Streaming Endpoint\" }\r\n ,\"microsoft.media/mediaservices/streaminglocators\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming locator\" }\r\n ,\"microsoft.media/mediaservices/streamingpolicies\": { \"SingularDisplayName\": \"Microsoft.Media media services streaming policy\" }\r\n ,\"microsoft.media/mediaservices/transforms\": { \"SingularDisplayName\": \"Microsoft.Media media services transform\" }\r\n ,\"microsoft.media/mediaservices/transforms/jobs\": { \"SingularDisplayName\": \"Microsoft.Media media services transforms job\" }\r\n ,\"microsoft.mesh/worlds\": { \"SingularDisplayName\": \"Microsoft.Mesh world\" }\r\n ,\"microsoft.mesh/worlds/events\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds event\" }\r\n ,\"microsoft.mesh/worlds/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds events access policy\" }\r\n ,\"microsoft.mesh/worlds/spaces\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds space\" }\r\n ,\"microsoft.mesh/worlds/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds spaces access policy\" }\r\n ,\"microsoft.mesh/worlds/templates\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds template\" }\r\n ,\"microsoft.mesh/worlds/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Mesh worlds templates access policy\" }\r\n ,\"microsoft.messagingcatalog/catalogs\": { \"SingularDisplayName\": \"Microsoft.MessagingCatalog catalog\" }\r\n ,\"microsoft.messagingconnectors/connectors\": { \"SingularDisplayName\": \"Microsoft.MessagingConnectors connector\" }\r\n ,\"microsoft.metaverse/metaverses\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverse\" }\r\n ,\"microsoft.metaverse/metaverses/events\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses event\" }\r\n ,\"microsoft.metaverse/metaverses/events/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses events access policy\" }\r\n ,\"microsoft.metaverse/metaverses/spaces\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses space\" }\r\n ,\"microsoft.metaverse/metaverses/spaces/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses spaces access policy\" }\r\n ,\"microsoft.metaverse/metaverses/templates\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses template\" }\r\n ,\"microsoft.metaverse/metaverses/templates/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.Metaverse metaverses templates access policy\" }\r\n ,\"microsoft.migrate/assessmentprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment project\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/clusters\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments cluster\" }\r\n ,\"microsoft.migrate/assessmentprojects/aksassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects aks assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/assessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments avs assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/avsassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects avs assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business case\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/avssummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases avs summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedavsmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated avs machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedsqlentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated sql entity\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/evaluatedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases evaluated web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/iaassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases iaas summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/overviewsummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases overview summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/businesscases/paassummaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects business cases paas summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects group\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/assessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/assessments/assessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups assessments assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/avsassessments/avsassessedmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups avs assessments avs assessed machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql database\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql instance\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments assessed sql machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/recommendedassessedentities\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments recommended assessed entity\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups sql assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/groups/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects groups web app assessments web app service plan\" }\r\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/heterogeneousassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects heterogeneous assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/hypervcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects hypervcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/importcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects importcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/importsqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects import sql collector\" }\r\n ,\"microsoft.migrate/assessmentprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private endpoint connection\" }\r\n ,\"microsoft.migrate/assessmentprojects/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects private link resource\" }\r\n ,\"microsoft.migrate/assessmentprojects/projectsummary\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects project summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/servercollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects servercollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqldatabases\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql database\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql instance\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/assessedsqlmachines\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments assessed sql machine\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sql assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/sqlcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects sqlcollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/vmwarecollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects vmwarecollector\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessmentoptions\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment option\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/assessedwebapps\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments assessed web app\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments summary\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappassessments/webappserviceplans\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app assessments web app service plan\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcollectors\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app collector\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessment\" }\r\n ,\"microsoft.migrate/assessmentprojects/webappcompoundassessments/summaries\": { \"SingularDisplayName\": \"Microsoft.Migrate assessment projects web app compound assessments summary\" }\r\n ,\"microsoft.migrate/migrateprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate project\" }\r\n ,\"microsoft.migrate/migrateprojects/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database instance\" }\r\n ,\"microsoft.migrate/migrateprojects/databases\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects database\" }\r\n ,\"microsoft.migrate/migrateprojects/machines\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects machine\" }\r\n ,\"microsoft.migrate/migrateprojects/migrateevents\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects migrate event\" }\r\n ,\"microsoft.migrate/migrateprojects/solutions\": { \"SingularDisplayName\": \"Microsoft.Migrate migrate projects solution\" }\r\n ,\"microsoft.migrate/modernizeprojects\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize project\" }\r\n ,\"microsoft.migrate/modernizeprojects/deployedresources\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects deployed resource\" }\r\n ,\"microsoft.migrate/modernizeprojects/jobs\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects job\" }\r\n ,\"microsoft.migrate/modernizeprojects/jobs/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects jobs operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/migrateagents\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agent\" }\r\n ,\"microsoft.migrate/modernizeprojects/migrateagents/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects migrate agents operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployment\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloaddeployments/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload deployments operation\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloadinstances\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instance\" }\r\n ,\"microsoft.migrate/modernizeprojects/workloadinstances/operations\": { \"SingularDisplayName\": \"Microsoft.Migrate modernize projects workload instances operation\" }\r\n ,\"microsoft.migrate/movecollections\": { \"SingularDisplayName\": \"Microsoft.Migrate move collection\" }\r\n ,\"microsoft.migrate/movecollections/moveresources\": { \"SingularDisplayName\": \"Microsoft.Migrate move collections move resource\" }\r\n ,\"microsoft.migrate/projects\": { \"SingularDisplayName\": \"Migration project\" }\r\n ,\"microsoft.mission/approvals\": { \"SingularDisplayName\": \"Approval\" }\r\n ,\"microsoft.mission/catalogs\": { \"SingularDisplayName\": \"Catalog\" }\r\n ,\"microsoft.mission/communities\": { \"SingularDisplayName\": \"Community\" }\r\n ,\"microsoft.mission/communities/communityendpoints\": { \"SingularDisplayName\": \"Community endpoint\" }\r\n ,\"microsoft.mission/communities/transithubs\": { \"SingularDisplayName\": \"Transit hub\" }\r\n ,\"microsoft.mission/enclaveconnections\": { \"SingularDisplayName\": \"Enclave connection\" }\r\n ,\"microsoft.mission/externalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission external connection\" }\r\n ,\"microsoft.mission/internalconnections\": { \"SingularDisplayName\": \"Microsoft.Mission internal connection\" }\r\n ,\"microsoft.mission/virtualenclaves\": { \"SingularDisplayName\": \"Enclave\" }\r\n ,\"microsoft.mission/virtualenclaves/enclaveendpoints\": { \"SingularDisplayName\": \"Enclave endpoint\" }\r\n ,\"microsoft.mission/virtualenclaves/endpoints\": { \"SingularDisplayName\": \"Endpoint\" }\r\n ,\"microsoft.mission/virtualenclaves/workloads\": { \"SingularDisplayName\": \"Workload\" }\r\n ,\"microsoft.mixedreality/objectanchorsaccounts\": { \"SingularDisplayName\": \"Object Anchors Account\" }\r\n ,\"microsoft.mixedreality/objectunderstandingaccounts\": { \"SingularDisplayName\": \"Object Understanding Account\" }\r\n ,\"microsoft.mixedreality/remoterenderingaccounts\": { \"SingularDisplayName\": \"Remote Rendering Account\" }\r\n ,\"microsoft.mixedreality/spatialanchorsaccounts\": { \"SingularDisplayName\": \"Spatial Anchors Account\" }\r\n ,\"microsoft.mixedreality/spatialmapsaccounts\": { \"SingularDisplayName\": \"Microsoft.MixedReality spatial maps account\" }\r\n ,\"microsoft.mobilenetwork/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork amf deployment\" }\r\n ,\"microsoft.mobilenetwork/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork cluster service\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks\": { \"SingularDisplayName\": \"Mobile Network\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/datanetworks\": { \"SingularDisplayName\": \"Data Network\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/services\": { \"SingularDisplayName\": \"Service\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/simpolicies\": { \"SingularDisplayName\": \"SIM Policy\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/sites\": { \"SingularDisplayName\": \"Mobile Network Site\" }\r\n ,\"microsoft.mobilenetwork/mobilenetworks/slices\": { \"SingularDisplayName\": \"Slice\" }\r\n ,\"microsoft.mobilenetwork/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nrf deployment\" }\r\n ,\"microsoft.mobilenetwork/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork nssf deployment\" }\r\n ,\"microsoft.mobilenetwork/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork observability service\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes\": { \"SingularDisplayName\": \"Packet Core Control Plane\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes\": { \"SingularDisplayName\": \"Packet Core Data Plane\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplanes/packetcoredataplanes/attacheddatanetworks\": { \"SingularDisplayName\": \"Attached Data Network\" }\r\n ,\"microsoft.mobilenetwork/packetcorecontrolplaneversions\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork packet core control plane version\" }\r\n ,\"microsoft.mobilenetwork/radioaccessnetworks\": { \"SingularDisplayName\": \"Radio Access Network Insights\" }\r\n ,\"microsoft.mobilenetwork/sdmdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sdm deployment\" }\r\n ,\"microsoft.mobilenetwork/simgroups\": { \"SingularDisplayName\": \"SIM Group\" }\r\n ,\"microsoft.mobilenetwork/simgroups/sims\": { \"SingularDisplayName\": \"SIM\" }\r\n ,\"microsoft.mobilenetwork/sims\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork sim\" }\r\n ,\"microsoft.mobilenetwork/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork smf deployment\" }\r\n ,\"microsoft.mobilenetwork/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork upf deployment\" }\r\n ,\"microsoft.mobilenetwork/virtualizedmmedeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork virtualized mme deployment\" }\r\n ,\"microsoft.mobilenetwork/vnfagentdeployments\": { \"SingularDisplayName\": \"Microsoft.MobileNetwork vnf agent deployment\" }\r\n ,\"microsoft.mobilepacketcore/amfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore amf deployment\" }\r\n ,\"microsoft.mobilepacketcore/clusterservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore cluster service\" }\r\n ,\"microsoft.mobilepacketcore/networkfunctions\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore network function\" }\r\n ,\"microsoft.mobilepacketcore/nrfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nrf deployment\" }\r\n ,\"microsoft.mobilepacketcore/nssfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore nssf deployment\" }\r\n ,\"microsoft.mobilepacketcore/observabilityservices\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore observability service\" }\r\n ,\"microsoft.mobilepacketcore/smfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore smf deployment\" }\r\n ,\"microsoft.mobilepacketcore/upfdeployments\": { \"SingularDisplayName\": \"Microsoft.MobilePacketCore upf deployment\" }\r\n ,\"microsoft.modsimworkbench/workbenches\": { \"SingularDisplayName\": \"Modeling and Simulation Workbench\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers\": { \"SingularDisplayName\": \"Chamber\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/connectors\": { \"SingularDisplayName\": \"Chamber Connector\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/filerequests\": { \"SingularDisplayName\": \"Chamber Data Pipeline File Request\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/files\": { \"SingularDisplayName\": \"Chamber Data Pipeline File\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/licenses\": { \"SingularDisplayName\": \"Chamber License\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/storages\": { \"SingularDisplayName\": \"Chamber Storage\" }\r\n ,\"microsoft.modsimworkbench/workbenches/chambers/workloads\": { \"SingularDisplayName\": \"Chamber VM\" }\r\n ,\"microsoft.modsimworkbench/workbenches/sharedstorages\": { \"SingularDisplayName\": \"Shared Storage\" }\r\n ,\"microsoft.monitor/accounts\": { \"SingularDisplayName\": \"Azure Monitor workspace\" }\r\n ,\"microsoft.monitor/investigations\": { \"SingularDisplayName\": \"Microsoft.Monitor investigation\" }\r\n ,\"microsoft.monitor/pipelinegroups\": { \"SingularDisplayName\": \"Azure Monitor pipeline\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsite\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/agents\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites agent\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites error summary\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/mysqlservers\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites my sqlserver\" }\r\n ,\"microsoft.mysqldiscovery/mysqlsites/summaries\": { \"SingularDisplayName\": \"Microsoft.MySQLDiscovery my sqlsites summary\" }\r\n ,\"microsoft.netapp/netappaccounts\": { \"SingularDisplayName\": \"NetApp account\" }\r\n ,\"microsoft.netapp/netappaccounts/backuppolicies\": { \"SingularDisplayName\": \"Backup Policy\" }\r\n ,\"microsoft.netapp/netappaccounts/backupvaults\": { \"SingularDisplayName\": \"Backup vault\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools\": { \"SingularDisplayName\": \"Capacity pool\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes\": { \"SingularDisplayName\": \"Volume\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/snapshots\": { \"SingularDisplayName\": \"Snapshot\" }\r\n ,\"microsoft.netapp/netappaccounts/capacitypools/volumes/volumequotarules\": { \"SingularDisplayName\": \"User and group quota\" }\r\n ,\"microsoft.netapp/netappaccounts/snapshotpolicies\": { \"SingularDisplayName\": \"Snapshot policy\" }\r\n ,\"microsoft.netapp/netappaccounts/volumegroups\": { \"SingularDisplayName\": \"VolumeGroup\" }\r\n ,\"microsoft.network/applicationgatewayavailablessloptions\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl option\" }\r\n ,\"microsoft.network/applicationgatewayavailablessloptions/predefinedpolicies\": { \"SingularDisplayName\": \"Microsoft.Network application gateway available ssl options predefined policy\" }\r\n ,\"microsoft.network/applicationgateways\": { \"SingularDisplayName\": \"Application gateway\" }\r\n ,\"microsoft.network/applicationgatewaywebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Application Gateway WAF policy\" }\r\n ,\"microsoft.network/applicationsecuritygroups\": { \"SingularDisplayName\": \"Application security group\" }\r\n ,\"microsoft.network/azurefirewalls\": { \"SingularDisplayName\": \"Firewall\" }\r\n ,\"microsoft.network/azurewebcategories\": { \"SingularDisplayName\": \"Microsoft.Network Azure web category\" }\r\n ,\"microsoft.network/bastionhosts\": { \"SingularDisplayName\": \"Bastion\" }\r\n ,\"microsoft.network/cloudserviceslots\": { \"SingularDisplayName\": \"Microsoft.Network cloud service slot\" }\r\n ,\"microsoft.network/connections\": { \"SingularDisplayName\": \"Connection\" }\r\n ,\"microsoft.network/customipprefixes\": { \"SingularDisplayName\": \"Custom IP Prefix\" }\r\n ,\"microsoft.network/ddoscustompolicies\": { \"SingularDisplayName\": \"Microsoft.Network DDoS custom policy\" }\r\n ,\"microsoft.network/ddosprotectionplans\": { \"SingularDisplayName\": \"DDoS protection plan\" }\r\n ,\"microsoft.network/dnsforwardingrulesets\": { \"SingularDisplayName\": \"DNS forwarding ruleset\" }\r\n ,\"microsoft.network/dnsresolverdomainlists\": { \"SingularDisplayName\": \"DNS Domain List\" }\r\n ,\"microsoft.network/dnsresolverpolicies\": { \"SingularDisplayName\": \"DNS Security Policy\" }\r\n ,\"microsoft.network/dnsresolvers\": { \"SingularDisplayName\": \"DNS private resolver\" }\r\n ,\"microsoft.network/dnszones\": { \"SingularDisplayName\": \"DNS zone\" }\r\n ,\"microsoft.network/dscpconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network DSCP configuration\" }\r\n ,\"microsoft.network/expressroutecircuits\": { \"SingularDisplayName\": \"ExpressRoute circuit\" }\r\n ,\"microsoft.network/expressroutecrossconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connection\" }\r\n ,\"microsoft.network/expressroutecrossconnections/peerings\": { \"SingularDisplayName\": \"Microsoft.Network express route cross connections peering\" }\r\n ,\"microsoft.network/expressroutegateways\": { \"SingularDisplayName\": \"ExpressRoute Gateway\" }\r\n ,\"microsoft.network/expressroutegateways/expressrouteconnections\": { \"SingularDisplayName\": \"Microsoft.Network express route gateways express route connection\" }\r\n ,\"microsoft.network/expressrouteports\": { \"SingularDisplayName\": \"ExpressRoute Direct\" }\r\n ,\"microsoft.network/expressrouteportslocations\": { \"SingularDisplayName\": \"Microsoft.Network express route ports location\" }\r\n ,\"microsoft.network/firewallpolicies\": { \"SingularDisplayName\": \"Firewall Policy\" }\r\n ,\"microsoft.network/frontdoors\": { \"SingularDisplayName\": \"Front Door and CDN profiles\" }\r\n ,\"microsoft.network/frontdoorwebapplicationfirewallpolicies\": { \"SingularDisplayName\": \"Front Door WAF policy\" }\r\n ,\"microsoft.network/ipallocations\": { \"SingularDisplayName\": \"Microsoft.Network IP allocation\" }\r\n ,\"microsoft.network/ipgroups\": { \"SingularDisplayName\": \"IP Group\" }\r\n ,\"microsoft.network/loadbalancers\": { \"SingularDisplayName\": \"Load balancer\" }\r\n ,\"microsoft.network/localnetworkgateways\": { \"SingularDisplayName\": \"Local network gateway\" }\r\n ,\"microsoft.network/natgateways\": { \"SingularDisplayName\": \"NAT gateway\" }\r\n ,\"microsoft.network/networkexperimentprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profile\" }\r\n ,\"microsoft.network/networkexperimentprofiles/experiments\": { \"SingularDisplayName\": \"Microsoft.Network network experiment profiles experiment\" }\r\n ,\"microsoft.network/networkinterfaces\": { \"SingularDisplayName\": \"Network interface\" }\r\n ,\"microsoft.network/networkmanagerconnections\": { \"SingularDisplayName\": \"Microsoft.Network network manager connection\" }\r\n ,\"microsoft.network/networkmanagers\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/connectivityconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/ipampools\": { \"SingularDisplayName\": \"IP address pool\" }\r\n ,\"microsoft.network/networkmanagers/networkgroups\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/routingconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/securityadminconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/securityuserconfigurations\": { \"SingularDisplayName\": \"Network manager\" }\r\n ,\"microsoft.network/networkmanagers/verifierworkspaces\": { \"SingularDisplayName\": \"Verifier Workspace\" }\r\n ,\"microsoft.network/networkprofiles\": { \"SingularDisplayName\": \"Microsoft.Network network profile\" }\r\n ,\"microsoft.network/networksecuritygroups\": { \"SingularDisplayName\": \"Network security group\" }\r\n ,\"microsoft.network/networksecurityperimeters\": { \"SingularDisplayName\": \"Network Security Perimeter\" }\r\n ,\"microsoft.network/networksecurityperimeters/profiles\": { \"SingularDisplayName\": \"Network Security Perimeter Profile\" }\r\n ,\"microsoft.network/networkverifiers\": { \"SingularDisplayName\": \"Virtual Network Verifier\" }\r\n ,\"microsoft.network/networkvirtualappliances\": { \"SingularDisplayName\": \"Microsoft.Network network virtual appliance\" }\r\n ,\"microsoft.network/networkwatchers\": { \"SingularDisplayName\": \"Network Watcher\" }\r\n ,\"microsoft.network/networkwatchers/flowlogs\": { \"SingularDisplayName\": \"Flow log\" }\r\n ,\"microsoft.network/p2svpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Point to Site)\" }\r\n ,\"microsoft.network/privatednszones\": { \"SingularDisplayName\": \"Private DNS zone\" }\r\n ,\"microsoft.network/privatednszones/virtualnetworklinks\": { \"SingularDisplayName\": \"Virtual network link\" }\r\n ,\"microsoft.network/privateendpoints\": { \"SingularDisplayName\": \"Private endpoint\" }\r\n ,\"microsoft.network/privatelinkservices\": { \"SingularDisplayName\": \"Private link service\" }\r\n ,\"microsoft.network/publicipaddresses\": { \"SingularDisplayName\": \"Public IP address\" }\r\n ,\"microsoft.network/publicipprefixes\": { \"SingularDisplayName\": \"Public IP Prefix\" }\r\n ,\"microsoft.network/routefilters\": { \"SingularDisplayName\": \"Route filter\" }\r\n ,\"microsoft.network/routetables\": { \"SingularDisplayName\": \"Route table\" }\r\n ,\"microsoft.network/securitypartnerproviders\": { \"SingularDisplayName\": \"Microsoft.Network security partner provider\" }\r\n ,\"microsoft.network/serviceendpointpolicies\": { \"SingularDisplayName\": \"Service endpoint policy\" }\r\n ,\"microsoft.network/trafficmanagergeographichierarchies\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager geographic hierarchy\" }\r\n ,\"microsoft.network/trafficmanagerprofiles\": { \"SingularDisplayName\": \"Traffic Manager profile\" }\r\n ,\"microsoft.network/trafficmanagerusermetricskeys\": { \"SingularDisplayName\": \"Microsoft.Network traffic manager user metrics key\" }\r\n ,\"microsoft.network/virtualhubs\": { \"SingularDisplayName\": \"Microsoft.Network/virtualHub\" }\r\n ,\"microsoft.network/virtualnetworkgateways\": { \"SingularDisplayName\": \"Virtual network gateway\" }\r\n ,\"microsoft.network/virtualnetworks\": { \"SingularDisplayName\": \"Virtual network\" }\r\n ,\"microsoft.network/virtualnetworktaps\": { \"SingularDisplayName\": \"Virtual network terminal access point\" }\r\n ,\"microsoft.network/virtualrouters\": { \"SingularDisplayName\": \"Microsoft.Network virtual router\" }\r\n ,\"microsoft.network/virtualrouters/peerings\": { \"SingularDisplayName\": \"Microsoft.Network virtual routers peering\" }\r\n ,\"microsoft.network/virtualwans\": { \"SingularDisplayName\": \"Virtual WAN\" }\r\n ,\"microsoft.network/vpngateways\": { \"SingularDisplayName\": \"VPN Gateway (Site to Site)\" }\r\n ,\"microsoft.network/vpngateways/vpnconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connection\" }\r\n ,\"microsoft.network/vpngateways/vpnconnections/vpnlinkconnections\": { \"SingularDisplayName\": \"Microsoft.Network VPN gateways VPN connections VPN link connection\" }\r\n ,\"microsoft.network/vpnserverconfigurations\": { \"SingularDisplayName\": \"Microsoft.Network VPN server configuration\" }\r\n ,\"microsoft.network/vpnsites\": { \"SingularDisplayName\": \"Microsoft.Network VPN site\" }\r\n ,\"microsoft.network/vpnsites/vpnsitelinks\": { \"SingularDisplayName\": \"Microsoft.Network VPN sites VPN site link\" }\r\n ,\"microsoft.networkanalytics/dataconnectors\": { \"SingularDisplayName\": \"AIOps - Data Connector\" }\r\n ,\"microsoft.networkanalytics/datalakehouses\": { \"SingularDisplayName\": \"AIOps - Data LakeHouse\" }\r\n ,\"microsoft.networkanalytics/dataproducts\": { \"SingularDisplayName\": \"Azure Operator Insights ? Data Product\" }\r\n ,\"microsoft.networkanalytics/dataproducts/datatypes\": { \"SingularDisplayName\": \"Data Type\" }\r\n ,\"microsoft.networkanalytics/dataproductscatalogs\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics data products catalog\" }\r\n ,\"microsoft.networkanalytics/metricsingestionendpoints\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics metrics ingestion endpoint\" }\r\n ,\"microsoft.networkanalytics/networkanalyticsproducts\": { \"SingularDisplayName\": \"Microsoft.NetworkAnalytics network analytics product\" }\r\n ,\"microsoft.networkcloud/baremetalmachines\": { \"SingularDisplayName\": \"Bare Metal Machine (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/cloudservicesnetworks\": { \"SingularDisplayName\": \"Cloud Services Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clustermanagers\": { \"SingularDisplayName\": \"Cluster Manager (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters\": { \"SingularDisplayName\": \"Cluster (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/baremetalmachinekeysets\": { \"SingularDisplayName\": \"Cluster Bare Metal Machine Key Set (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/bmckeysets\": { \"SingularDisplayName\": \"Cluster Baseboard Management Controller Key Set (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/clusters/metricsconfigurations\": { \"SingularDisplayName\": \"Cluster Metrics Configuration (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/edgeclustermachineskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster machine SKU\" }\r\n ,\"microsoft.networkcloud/edgeclusterruntimeversions\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster runtime version\" }\r\n ,\"microsoft.networkcloud/edgeclusters\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster\" }\r\n ,\"microsoft.networkcloud/edgeclusters/nodes\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge clusters node\" }\r\n ,\"microsoft.networkcloud/edgeclusterskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud edge cluster SKU\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters\": { \"SingularDisplayName\": \"Kubernetes Cluster (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters/agentpools\": { \"SingularDisplayName\": \"Agent Pool (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/kubernetesclusters/features\": { \"SingularDisplayName\": \"Kubernetes Cluster Feature (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/l2networks\": { \"SingularDisplayName\": \"Layer 2 Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/l3networks\": { \"SingularDisplayName\": \"Layer 3 Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/racks\": { \"SingularDisplayName\": \"Compute Rack (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/rackskus\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud rack SKU\" }\r\n ,\"microsoft.networkcloud/registrationhubs\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hub\" }\r\n ,\"microsoft.networkcloud/registrationhubs/images\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs image\" }\r\n ,\"microsoft.networkcloud/registrationhubs/machines\": { \"SingularDisplayName\": \"Microsoft.NetworkCloud registration hubs machine\" }\r\n ,\"microsoft.networkcloud/storageappliances\": { \"SingularDisplayName\": \"Storage Appliance (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/trunkednetworks\": { \"SingularDisplayName\": \"Trunked Network (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/virtualmachines\": { \"SingularDisplayName\": \"Virtual Machine (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/virtualmachines/consoles\": { \"SingularDisplayName\": \"Virtual Machine Console (Operator Nexus)\" }\r\n ,\"microsoft.networkcloud/volumes\": { \"SingularDisplayName\": \"Volume (Operator Nexus)\" }\r\n ,\"microsoft.networkfunction/azuretrafficcollectors\": { \"SingularDisplayName\": \"ExpressRoute traffic collector\" }\r\n ,\"microsoft.networkfunction/meshvpns\": { \"SingularDisplayName\": \"Mesh VPN\" }\r\n ,\"microsoft.nexusidentity/identitycontrollers\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity controller\" }\r\n ,\"microsoft.nexusidentity/identitysets\": { \"SingularDisplayName\": \"Microsoft.NexusIdentity identity set\" }\r\n ,\"microsoft.notebooks/notebookproxies\": { \"SingularDisplayName\": \"Microsoft.Notebooks notebook proxy\" }\r\n ,\"microsoft.notificationhubs/namespaces\": { \"SingularDisplayName\": \"Notification Hub Namespace\" }\r\n ,\"microsoft.notificationhubs/namespaces/notificationhubs\": { \"SingularDisplayName\": \"Notification Hub\" }\r\n ,\"microsoft.objectstore/osnamespaces\": { \"SingularDisplayName\": \"Microsoft.ObjectStore os namespace\" }\r\n })[tolower(id)]\r\n}\r\n", + "$fxv#3": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_4(id: string) {\r\n dynamic({\r\n \"microsoft.offazure/hypervsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv site\" }\r\n ,\"microsoft.offazure/hypervsites/clusters\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites cluster\" }\r\n ,\"microsoft.offazure/hypervsites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites host\" }\r\n ,\"microsoft.offazure/hypervsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites job\" }\r\n ,\"microsoft.offazure/hypervsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machine\" }\r\n ,\"microsoft.offazure/hypervsites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites machines software inventory\" }\r\n ,\"microsoft.offazure/hypervsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites operations statu\" }\r\n ,\"microsoft.offazure/hypervsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure hyperv sites run as account\" }\r\n ,\"microsoft.offazure/importsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure import site\" }\r\n ,\"microsoft.offazure/importsites/deletejobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites delete job\" }\r\n ,\"microsoft.offazure/importsites/exportjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites export job\" }\r\n ,\"microsoft.offazure/importsites/importjobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites import job\" }\r\n ,\"microsoft.offazure/importsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites job\" }\r\n ,\"microsoft.offazure/importsites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure import sites machine\" }\r\n ,\"microsoft.offazure/mastersites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master site\" }\r\n ,\"microsoft.offazure/mastersites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites operations statu\" }\r\n ,\"microsoft.offazure/mastersites/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private endpoint connection\" }\r\n ,\"microsoft.offazure/mastersites/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites private link resource\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql site\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites discovery site data source\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites job\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites operations statu\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites run as account\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqlavailabilitygroups\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql availability group\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqldatabases\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql database\" }\r\n ,\"microsoft.offazure/mastersites/sqlsites/sqlservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites sql sites sql server\" }\r\n ,\"microsoft.offazure/mastersites/webappsites\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app site\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/discoverysitedatasources\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites discovery site data source\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/extendedmachines\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites extended machine\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/iiswebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web application\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/iiswebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites iis web server\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites runasaccount\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebapplications\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web application\" }\r\n ,\"microsoft.offazure/mastersites/webappsites/tomcatwebservers\": { \"SingularDisplayName\": \"Microsoft.OffAzure master sites web app sites tomcat web server\" }\r\n ,\"microsoft.offazure/serversites\": { \"SingularDisplayName\": \"Microsoft.OffAzure server site\" }\r\n ,\"microsoft.offazure/serversites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites job\" }\r\n ,\"microsoft.offazure/serversites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machine\" }\r\n ,\"microsoft.offazure/serversites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites machines software inventory\" }\r\n ,\"microsoft.offazure/serversites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites operations statu\" }\r\n ,\"microsoft.offazure/serversites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure server sites run as account\" }\r\n ,\"microsoft.offazure/vmwaresites\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware site\" }\r\n ,\"microsoft.offazure/vmwaresites/hosts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites host\" }\r\n ,\"microsoft.offazure/vmwaresites/jobs\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites job\" }\r\n ,\"microsoft.offazure/vmwaresites/machines\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machine\" }\r\n ,\"microsoft.offazure/vmwaresites/machines/softwareinventories\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites machines software inventory\" }\r\n ,\"microsoft.offazure/vmwaresites/operationsstatus\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites operations statu\" }\r\n ,\"microsoft.offazure/vmwaresites/runasaccounts\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites run as account\" }\r\n ,\"microsoft.offazure/vmwaresites/vcenters\": { \"SingularDisplayName\": \"Microsoft.OffAzure vmware sites vcenter\" }\r\n ,\"microsoft.offazurespringboot/springbootsites\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsite\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites error summary\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/springbootapps\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootapp\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/springbootservers\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites springbootserver\" }\r\n ,\"microsoft.offazurespringboot/springbootsites/summaries\": { \"SingularDisplayName\": \"Microsoft.OffAzureSpringBoot springbootsites summary\" }\r\n ,\"microsoft.onlineexperimentation/workspaces\": { \"SingularDisplayName\": \"Online Experimentation Workspace\" }\r\n ,\"microsoft.openenergyplatform/energyservices\": { \"SingularDisplayName\": \"Azure Data Manager for Energy\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspace\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/applicationregistrations\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application registration\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/applications\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces application\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/eventgridfilters\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces event grid filter\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/shares\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share\" }\r\n ,\"microsoft.openlogisticsplatform/workspaces/sharesubscriptions\": { \"SingularDisplayName\": \"Microsoft.OpenLogisticsPlatform workspaces share subscription\" }\r\n ,\"microsoft.operationalinsights/clusters\": { \"SingularDisplayName\": \"Log Analytics dedicated cluster\" }\r\n ,\"microsoft.operationalinsights/querypacks\": { \"SingularDisplayName\": \"Log Analytics query pack\" }\r\n ,\"microsoft.operationalinsights/workspaces\": { \"SingularDisplayName\": \"Log Analytics workspace\" }\r\n ,\"microsoft.operationsmanagement/managementassociations\": { \"SingularDisplayName\": \"Microsoft.OperationsManagement management association\" }\r\n ,\"microsoft.operationsmanagement/solutions\": { \"SingularDisplayName\": \"Solution\" }\r\n ,\"microsoft.operatorvoicemail/operatorvoicemailinstances\": { \"SingularDisplayName\": \"Microsoft.OperatorVoicemail operator voicemail instance\" }\r\n ,\"microsoft.oraclediscovery/oraclesites\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle site\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/errorsummaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites error summary\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/oracledatabases\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle database\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/oracleservers\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites oracle server\" }\r\n ,\"microsoft.oraclediscovery/oraclesites/summaries\": { \"SingularDisplayName\": \"Microsoft.OracleDiscovery oracle sites summary\" }\r\n ,\"microsoft.orbital/cloudaccessrouters\": { \"SingularDisplayName\": \"Cloud Access Router\" }\r\n ,\"microsoft.orbital/contactprofiles\": { \"SingularDisplayName\": \"Contact Profile\" }\r\n ,\"microsoft.orbital/edgesites\": { \"SingularDisplayName\": \"Edge Site\" }\r\n ,\"microsoft.orbital/geocatalogs\": { \"SingularDisplayName\": \"GeoCatalog\" }\r\n ,\"microsoft.orbital/globalcommunicationssites\": { \"SingularDisplayName\": \"Microsoft.Orbital global communications site\" }\r\n ,\"microsoft.orbital/groundstations\": { \"SingularDisplayName\": \"Ground Station\" }\r\n ,\"microsoft.orbital/l2connections\": { \"SingularDisplayName\": \"L2 Connection\" }\r\n ,\"microsoft.orbital/sdwancontrollers\": { \"SingularDisplayName\": \"SDWAN Controller\" }\r\n ,\"microsoft.orbital/spacecrafts\": { \"SingularDisplayName\": \"Spacecraft\" }\r\n ,\"microsoft.orbital/spacecrafts/contacts\": { \"SingularDisplayName\": \"Contact\" }\r\n ,\"microsoft.orbital/terminals\": { \"SingularDisplayName\": \"Cloud Access Terminal\" }\r\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrence\" }\r\n ,\"microsoft.partnermanagedconsumerrecurrence/recurrences/operationresult\": { \"SingularDisplayName\": \"Microsoft.PartnerManagedConsumerRecurrence recurrences operation result\" }\r\n ,\"microsoft.peering/peerasns\": { \"SingularDisplayName\": \"Microsoft.Peering peer asn\" }\r\n ,\"microsoft.peering/peerings\": { \"SingularDisplayName\": \"Peering\" }\r\n ,\"microsoft.peering/peerings/registeredasns\": { \"SingularDisplayName\": \"Registered ASN\" }\r\n ,\"microsoft.peering/peerings/registeredprefixes\": { \"SingularDisplayName\": \"Registered prefix\" }\r\n ,\"microsoft.peering/peeringservices\": { \"SingularDisplayName\": \"Peering Service\" }\r\n ,\"microsoft.peering/peeringservices/prefixes\": { \"SingularDisplayName\": \"Peering Service Prefix\" }\r\n ,\"microsoft.pki/pkis\": { \"SingularDisplayName\": \"Microsoft.Pki PKI\" }\r\n ,\"microsoft.pki/pkis/certificateauthorities\": { \"SingularDisplayName\": \"Microsoft.Pki pkis certificate authority\" }\r\n ,\"microsoft.pki/pkis/enrollmentpolicies\": { \"SingularDisplayName\": \"Microsoft.Pki pkis enrollment policy\" }\r\n ,\"microsoft.policyinsights/attestations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights attestation\" }\r\n ,\"microsoft.policyinsights/policymetadata\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights policy metadata\" }\r\n ,\"microsoft.policyinsights/remediations\": { \"SingularDisplayName\": \"Microsoft.PolicyInsights remediation\" }\r\n ,\"microsoft.portal/consoles\": { \"SingularDisplayName\": \"Microsoft.Portal console\" }\r\n ,\"microsoft.portal/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\r\n ,\"microsoft.portal/tenantconfigurations\": { \"SingularDisplayName\": \"Microsoft.Portal tenant configuration\" }\r\n ,\"microsoft.portal/usersettings\": { \"SingularDisplayName\": \"Microsoft.Portal user setting\" }\r\n ,\"microsoft.portal/virtual-privatedashboards\": { \"SingularDisplayName\": \"Private dashboard\" }\r\n ,\"microsoft.portalservices/copilotsettings\": { \"SingularDisplayName\": \"Microsoft.PortalServices copilot setting\" }\r\n ,\"microsoft.portalservices/dashboards\": { \"SingularDisplayName\": \"Shared dashboard\" }\r\n ,\"microsoft.portalservices/extensions\": { \"SingularDisplayName\": \"Portal Extension\" }\r\n ,\"microsoft.portalservices/extensions/deployments\": { \"SingularDisplayName\": \"Extension Deployment\" }\r\n ,\"microsoft.portalservices/extensions/slots\": { \"SingularDisplayName\": \"Extension Slot\" }\r\n ,\"microsoft.portalservices/extensions/versions\": { \"SingularDisplayName\": \"Extension Version\" }\r\n ,\"microsoft.portalservices/settings\": { \"SingularDisplayName\": \"Microsoft.PortalServices setting\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privateendpointconnections\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private endpoint connection\" }\r\n ,\"microsoft.powerbi/privatelinkservicesforpowerbi/privatelinkresources\": { \"SingularDisplayName\": \"Microsoft.PowerBI private link services for power bi private link resource\" }\r\n ,\"microsoft.powerbi/workspacecollections\": { \"SingularDisplayName\": \"Microsoft.PowerBI workspace collection\" }\r\n ,\"microsoft.powerbidedicated/autoscalevcores\": { \"SingularDisplayName\": \"Microsoft.PowerBIDedicated auto scale vcore\" }\r\n ,\"microsoft.powerbidedicated/capacities\": { \"SingularDisplayName\": \"Power BI Embedded\" }\r\n ,\"microsoft.powerplatform/accounts\": { \"SingularDisplayName\": \"Microsoft.PowerPlatform account\" }\r\n ,\"microsoft.premonition/libraries\": { \"SingularDisplayName\": \"Microsoft.Premonition library\" }\r\n ,\"microsoft.premonition/libraries/analyses\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries analyse\" }\r\n ,\"microsoft.premonition/libraries/samples\": { \"SingularDisplayName\": \"Microsoft.Premonition libraries sample\" }\r\n ,\"microsoft.professionalservice/resources\": { \"SingularDisplayName\": \"Professional Service\" }\r\n ,\"microsoft.programmableconnectivity/gateways\": { \"SingularDisplayName\": \"APC Gateway\" }\r\n ,\"microsoft.programmableconnectivity/operatorapiconnections\": { \"SingularDisplayName\": \"APC Operator API Connection\" }\r\n ,\"microsoft.programmableconnectivity/operatorapiplans\": { \"SingularDisplayName\": \"APC Operator API Plan\" }\r\n ,\"microsoft.proposal/proposals\": { \"SingularDisplayName\": \"Microsoft.Proposal proposal\" }\r\n ,\"microsoft.providerhub/providerregistrations\": { \"SingularDisplayName\": \"Resource Provider as a Service\" }\r\n ,\"microsoft.providerhub/providerregistrations/customrollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.providerhub/providerregistrations/defaultrollouts\": { \"SingularDisplayName\": \"Rollout\" }\r\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\r\n ,\"microsoft.providerhub/providerregistrations/resourcetyperegistrations/resourcetyperegistrations\": { \"SingularDisplayName\": \"Resource Type\" }\r\n ,\"microsoft.providerhubdevtest/regionalstresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest regional stresstest\" }\r\n ,\"microsoft.providerhubdevtest/stresstests\": { \"SingularDisplayName\": \"Microsoft.ProviderHubDevTest stresstest\" }\r\n ,\"microsoft.purview/accounts\": { \"SingularDisplayName\": \"Microsoft Purview account\" }\r\n ,\"microsoft.quantum/provideraccounts\": { \"SingularDisplayName\": \"Microsoft.Quantum provider account\" }\r\n ,\"microsoft.quantum/workspaces\": { \"SingularDisplayName\": \"Quantum Workspace\" }\r\n ,\"microsoft.quota/groupquotas\": { \"SingularDisplayName\": \"Microsoft.Quota group quota\" }\r\n ,\"microsoft.quota/groupquotas/groupquotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas group quota request\" }\r\n ,\"microsoft.quota/groupquotas/quotaallocationrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation request\" }\r\n ,\"microsoft.quota/groupquotas/quotaallocations\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas quota allocation\" }\r\n ,\"microsoft.quota/groupquotas/subscriptionrequests\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription request\" }\r\n ,\"microsoft.quota/groupquotas/subscriptions\": { \"SingularDisplayName\": \"Microsoft.Quota group quotas subscription\" }\r\n ,\"microsoft.quota/quotarequests\": { \"SingularDisplayName\": \"Microsoft.Quota quota request\" }\r\n ,\"microsoft.quota/quotas\": { \"SingularDisplayName\": \"Microsoft.Quota quota\" }\r\n ,\"microsoft.quota/usages\": { \"SingularDisplayName\": \"Microsoft.Quota usage\" }\r\n ,\"microsoft.recommendationsservice/accounts\": { \"SingularDisplayName\": \"Intelligent Recommendations Account\" }\r\n ,\"microsoft.recommendationsservice/accounts/modeling\": { \"SingularDisplayName\": \"Modeling\" }\r\n ,\"microsoft.recommendationsservice/accounts/serviceendpoints\": { \"SingularDisplayName\": \"Service Endpoint\" }\r\n ,\"microsoft.recoveryservices/replicationeligibilityresults\": { \"SingularDisplayName\": \"Microsoft.RecoveryServices replication eligibility result\" }\r\n ,\"microsoft.recoveryservices/vaults\": { \"SingularDisplayName\": \"Recovery Services vault\" }\r\n ,\"microsoft.recoveryservices/vaults/backupfabrics/protectioncontainers/protecteditems\": { \"SingularDisplayName\": \"Backup Item\" }\r\n ,\"microsoft.recoveryservicesbvtd/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD\" }\r\n ,\"microsoft.recoveryservicesbvtd2/vaults\": { \"SingularDisplayName\": \"Recovery Services BVTD2\" }\r\n ,\"microsoft.recoveryservicesintd/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD\" }\r\n ,\"microsoft.recoveryservicesintd2/vaults\": { \"SingularDisplayName\": \"Recovery Services INTD2\" }\r\n ,\"microsoft.redhatopenshift/openshiftclusters\": { \"SingularDisplayName\": \"Azure Red Hat OpenShift cluster\" }\r\n ,\"microsoft.relationships/dependencyof\": { \"SingularDisplayName\": \"Dependency Relationship\" }\r\n ,\"microsoft.relationships/servicegroupmember\": { \"SingularDisplayName\": \"Service group member relationship\" }\r\n ,\"microsoft.relationships/servicegrouprelationships\": { \"SingularDisplayName\": \"Connected Resource\" }\r\n ,\"microsoft.relay/namespaces\": { \"SingularDisplayName\": \"Relay\" }\r\n ,\"microsoft.relay/namespaces/hybridconnections\": { \"SingularDisplayName\": \"Hybrid connection\" }\r\n ,\"microsoft.relay/namespaces/wcfrelays\": { \"SingularDisplayName\": \"WCF relay\" }\r\n ,\"microsoft.resilience/resiliencestates\": { \"SingularDisplayName\": \"Microsoft.Resilience resilience state\" }\r\n ,\"microsoft.resourceconnector/appliances\": { \"SingularDisplayName\": \"Resource bridge\" }\r\n ,\"microsoft.resourcegraph/queries\": { \"SingularDisplayName\": \"Resource Graph query\" }\r\n ,\"microsoft.resourcehealth/availabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth availability statuse\" }\r\n ,\"microsoft.resourcehealth/childavailabilitystatuses\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth child availability statuse\" }\r\n ,\"microsoft.resourcehealth/emergingissues\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth emerging issue\" }\r\n ,\"microsoft.resourcehealth/events\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth event\" }\r\n ,\"microsoft.resourcehealth/events/impactedresources\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth events impacted resource\" }\r\n ,\"microsoft.resourcehealth/metadata\": { \"SingularDisplayName\": \"Microsoft.ResourceHealth metadata\" }\r\n ,\"microsoft.resources/builtintemplatespecs\": { \"SingularDisplayName\": \"Built-in template spec\" }\r\n ,\"microsoft.resources/changes\": { \"SingularDisplayName\": \"Microsoft.Resources change\" }\r\n ,\"microsoft.resources/databoundaries\": { \"SingularDisplayName\": \"Microsoft.Resources data boundary\" }\r\n ,\"microsoft.resources/deletedresources\": { \"SingularDisplayName\": \"Recycle Bin\" }\r\n ,\"microsoft.resources/deployments\": { \"SingularDisplayName\": \"Microsoft.Resources deployment\" }\r\n ,\"microsoft.resources/deployments/operations\": { \"SingularDisplayName\": \"Microsoft.Resources deployments operation\" }\r\n ,\"microsoft.resources/deploymentscripts\": { \"SingularDisplayName\": \"Deployment Script\" }\r\n ,\"microsoft.resources/deploymentstacks\": { \"SingularDisplayName\": \"Deployment stack\" }\r\n ,\"microsoft.resources/mobobrokers\": { \"SingularDisplayName\": \"Microsoft.Resources mobo broker\" }\r\n ,\"microsoft.resources/resourcechange\": { \"SingularDisplayName\": \"Change Analysis\" }\r\n ,\"microsoft.resources/resourcechanges\": { \"SingularDisplayName\": \"Resource change\" }\r\n ,\"microsoft.resources/resourcegraphvisualizer\": { \"SingularDisplayName\": \"Resource Graph Visualizer\" }\r\n ,\"microsoft.resources/resourcegroups\": { \"SingularDisplayName\": \"Microsoft.Resources resource group\" }\r\n ,\"microsoft.resources/resources\": { \"SingularDisplayName\": \"Resource\" }\r\n ,\"microsoft.resources/snapshots\": { \"SingularDisplayName\": \"Microsoft.Resources snapshot\" }\r\n ,\"microsoft.resources/subscriptions\": { \"SingularDisplayName\": \"Subscription\" }\r\n ,\"microsoft.resources/subscriptions/resourcegroups\": { \"SingularDisplayName\": \"Resource group\" }\r\n ,\"microsoft.resources/tags\": { \"SingularDisplayName\": \"Microsoft.Resources tag\" }\r\n ,\"microsoft.resources/templatespecs\": { \"SingularDisplayName\": \"Template spec\" }\r\n ,\"microsoft.resources/virtualsubscriptionsforresourcepicker\": { \"SingularDisplayName\": \"Subscription\" }\r\n ,\"microsoft.saas/applications\": { \"SingularDisplayName\": \"Software as a Service (classic)\" }\r\n ,\"microsoft.saas/resources\": { \"SingularDisplayName\": \"SaaS\" }\r\n ,\"microsoft.saas/saasresources\": { \"SingularDisplayName\": \"SaaS (classic)\" }\r\n ,\"microsoft.saashub/cloudservices\": { \"SingularDisplayName\": \"Microsoft.SaaSHub cloud service\" }\r\n ,\"microsoft.saashub/cloudservices/hidden\": { \"SingularDisplayName\": \"Microsoft SaaS\" }\r\n ,\"microsoft.saashub/saasresources\": { \"SingularDisplayName\": \"Microsoft.SaaSHub saas resource\" }\r\n ,\"microsoft.salescopilot/conversationintelligencerecordingaccounts\": { \"SingularDisplayName\": \"Microsoft.SalesCopilot conversation intelligence recording account\" }\r\n ,\"microsoft.scheduler/jobcollections\": { \"SingularDisplayName\": \"Scheduler job collection\" }\r\n ,\"microsoft.scheduler/jobcollections/jobs\": { \"SingularDisplayName\": \"Scheduler job\" }\r\n ,\"microsoft.scom/managedinstances\": { \"SingularDisplayName\": \"SCOM managed instance\" }\r\n ,\"microsoft.scvmm/availabilitysets\": { \"SingularDisplayName\": \"Microsoft.ScVmm availability set\" }\r\n ,\"microsoft.scvmm/clouds\": { \"SingularDisplayName\": \"Microsoft.ScVmm cloud\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instance\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances/guestagents\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances guest agent\" }\r\n ,\"microsoft.scvmm/virtualmachineinstances/hybrididentitymetadata\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine instances hybrid identity metadata\" }\r\n ,\"microsoft.scvmm/virtualmachines\": { \"SingularDisplayName\": \"SCVMM virtual machine - Azure Arc\" }\r\n ,\"microsoft.scvmm/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual machine template\" }\r\n ,\"microsoft.scvmm/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.ScVmm virtual network\" }\r\n ,\"microsoft.scvmm/vmmservers\": { \"SingularDisplayName\": \"SCVMM management server\" }\r\n ,\"microsoft.search/searchservices\": { \"SingularDisplayName\": \"Search service\" }\r\n ,\"microsoft.secretmanagementsampleprovider/forecasts\": { \"SingularDisplayName\": \"Microsoft.SecretManagementSampleProvider forecast\" }\r\n ,\"microsoft.secretsynccontroller/azurekeyvaultsecretproviderclasses\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController Azure key vault secret provider class\" }\r\n ,\"microsoft.secretsynccontroller/secretsyncs\": { \"SingularDisplayName\": \"Microsoft.SecretSyncController secret sync\" }\r\n ,\"microsoft.security/adaptivenetworkhardenings\": { \"SingularDisplayName\": \"Microsoft.Security adaptive network hardening\" }\r\n ,\"microsoft.security/advancedthreatprotectionsettings\": { \"SingularDisplayName\": \"Microsoft.Security advanced threat protection setting\" }\r\n ,\"microsoft.security/alertssuppressionrules\": { \"SingularDisplayName\": \"Microsoft.Security alerts suppression rule\" }\r\n ,\"microsoft.security/apicollections\": { \"SingularDisplayName\": \"Microsoft.Security API collection\" }\r\n ,\"microsoft.security/applications\": { \"SingularDisplayName\": \"Microsoft.Security application\" }\r\n ,\"microsoft.security/assessmentmetadata\": { \"SingularDisplayName\": \"Microsoft.Security assessment metadata\" }\r\n ,\"microsoft.security/assessments\": { \"SingularDisplayName\": \"Microsoft.Security assessment\" }\r\n ,\"microsoft.security/assessments/governanceassignments\": { \"SingularDisplayName\": \"Microsoft.Security assessments governance assignment\" }\r\n ,\"microsoft.security/assessments/subassessments\": { \"SingularDisplayName\": \"Microsoft.Security assessments sub assessment\" }\r\n ,\"microsoft.security/assignments\": { \"SingularDisplayName\": \"Microsoft.Security assignment\" }\r\n ,\"microsoft.security/automations\": { \"SingularDisplayName\": \"Microsoft.Security automation\" }\r\n ,\"microsoft.security/autoprovisioningsettings\": { \"SingularDisplayName\": \"Microsoft.Security auto provisioning setting\" }\r\n ,\"microsoft.security/complianceresults\": { \"SingularDisplayName\": \"Microsoft.Security compliance result\" }\r\n ,\"microsoft.security/compliances\": { \"SingularDisplayName\": \"Microsoft.Security compliance\" }\r\n ,\"microsoft.security/connectors\": { \"SingularDisplayName\": \"Microsoft.Security connector\" }\r\n ,\"microsoft.security/customassessmentautomations\": { \"SingularDisplayName\": \"Microsoft.Security custom assessment automation\" }\r\n ,\"microsoft.security/defenderforstoragesettings\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage setting\" }\r\n ,\"microsoft.security/defenderforstoragesettings/malwarescans\": { \"SingularDisplayName\": \"Microsoft.Security defender for storage settings malware scan\" }\r\n ,\"microsoft.security/devicesecuritygroups\": { \"SingularDisplayName\": \"Microsoft.Security device security group\" }\r\n ,\"microsoft.security/governancerules\": { \"SingularDisplayName\": \"Microsoft.Security governance rule\" }\r\n ,\"microsoft.security/governancerules/operationresults\": { \"SingularDisplayName\": \"Microsoft.Security governance rules operation result\" }\r\n ,\"microsoft.security/healthreports\": { \"SingularDisplayName\": \"Microsoft.Security health report\" }\r\n ,\"microsoft.security/informationprotectionpolicies\": { \"SingularDisplayName\": \"Microsoft.Security information protection policy\" }\r\n ,\"microsoft.security/iotsecuritysolutions\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solution\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics model\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated alert\" }\r\n ,\"microsoft.security/iotsecuritysolutions/analyticsmodels/aggregatedrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions analytics models aggregated recommendation\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotalerts\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotalerttypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT alert type\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendations\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation\" }\r\n ,\"microsoft.security/iotsecuritysolutions/iotrecommendationtypes\": { \"SingularDisplayName\": \"Microsoft.Security IoT security solutions IoT recommendation type\" }\r\n ,\"microsoft.security/locations/alerts\": { \"SingularDisplayName\": \"Security Alert\" }\r\n ,\"microsoft.security/mdeonboardings\": { \"SingularDisplayName\": \"Microsoft.Security mde onboarding\" }\r\n ,\"microsoft.security/pricings\": { \"SingularDisplayName\": \"Defender for Cloud\" }\r\n ,\"microsoft.security/pricings/securityoperators\": { \"SingularDisplayName\": \"Microsoft.Security pricings security operator\" }\r\n ,\"microsoft.security/regulatorycompliancestandards\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standard\" }\r\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance control\" }\r\n ,\"microsoft.security/regulatorycompliancestandards/regulatorycompliancecontrols/regulatorycomplianceassessments\": { \"SingularDisplayName\": \"Microsoft.Security regulatory compliance standards regulatory compliance controls regulatory compliance assessment\" }\r\n ,\"microsoft.security/securescores\": { \"SingularDisplayName\": \"Microsoft.Security secure score\" }\r\n ,\"microsoft.security/securityconnectors\": { \"SingularDisplayName\": \"Microsoft.Security security connector\" }\r\n ,\"microsoft.security/securityconnectors/devops\": { \"SingularDisplayName\": \"Microsoft.Security security connectors devop\" }\r\n ,\"microsoft.security/securitycontacts\": { \"SingularDisplayName\": \"Microsoft.Security security contact\" }\r\n ,\"microsoft.security/sensitivitysettings\": { \"SingularDisplayName\": \"Microsoft.Security sensitivity setting\" }\r\n ,\"microsoft.security/servervulnerabilityassessments\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessment\" }\r\n ,\"microsoft.security/servervulnerabilityassessmentssettings\": { \"SingularDisplayName\": \"Microsoft.Security server vulnerability assessments setting\" }\r\n ,\"microsoft.security/settings\": { \"SingularDisplayName\": \"Microsoft.Security setting\" }\r\n ,\"microsoft.security/standards\": { \"SingularDisplayName\": \"Microsoft.Security standard\" }\r\n ,\"microsoft.security/workspacesettings\": { \"SingularDisplayName\": \"Microsoft.Security workspace setting\" }\r\n ,\"microsoft.securitycopilot/capacities\": { \"SingularDisplayName\": \"Microsoft Security compute capacity\" }\r\n ,\"microsoft.securitydetonation/chambers\": { \"SingularDisplayName\": \"Security Detonation Chamber\" }\r\n ,\"microsoft.securityinsightsarg/sentinel\": { \"SingularDisplayName\": \"Microsoft Sentinel\" }\r\n ,\"microsoft.sentinelplatformservices/sentinelplatformservices\": { \"SingularDisplayName\": \"Microsoft.SentinelPlatformServices sentinel platform service\" }\r\n ,\"microsoft.serialconsole/consoleservices\": { \"SingularDisplayName\": \"Microsoft.SerialConsole console service\" }\r\n ,\"microsoft.serialconsole/serialports\": { \"SingularDisplayName\": \"Microsoft.SerialConsole serial port\" }\r\n ,\"microsoft.servicebus/namespaces\": { \"SingularDisplayName\": \"Service Bus namespace\" }\r\n ,\"microsoft.servicebus/namespaces/disasterrecoveryconfigs\": { \"SingularDisplayName\": \"Service Bus Geo-DR Alias\" }\r\n ,\"microsoft.servicebus/namespaces/queues\": { \"SingularDisplayName\": \"Service Bus queue\" }\r\n ,\"microsoft.servicebus/namespaces/topics\": { \"SingularDisplayName\": \"Service Bus topic\" }\r\n ,\"microsoft.servicebus/namespaces/topics/subscriptions\": { \"SingularDisplayName\": \"Service Bus Subscription\" }\r\n ,\"microsoft.servicefabric/clusters\": { \"SingularDisplayName\": \"Service Fabric cluster\" }\r\n ,\"microsoft.servicefabric/managedclusters\": { \"SingularDisplayName\": \"Service Fabric managed cluster\" }\r\n ,\"microsoft.servicefabricmesh/applications\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh application\" }\r\n ,\"microsoft.servicefabricmesh/applications/services\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications service\" }\r\n ,\"microsoft.servicefabricmesh/applications/services/replicas\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh applications services replica\" }\r\n ,\"microsoft.servicefabricmesh/gateways\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh gateway\" }\r\n ,\"microsoft.servicefabricmesh/networks\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh network\" }\r\n ,\"microsoft.servicefabricmesh/secrets\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secret\" }\r\n ,\"microsoft.servicefabricmesh/secrets/values\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh secrets value\" }\r\n ,\"microsoft.servicefabricmesh/volumes\": { \"SingularDisplayName\": \"Microsoft.ServiceFabricMesh volume\" }\r\n ,\"microsoft.servicelinker/dryruns\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker dryrun\" }\r\n ,\"microsoft.servicelinker/linkers\": { \"SingularDisplayName\": \"Microsoft.ServiceLinker linker\" }\r\n ,\"microsoft.servicenetworking/trafficcontrollers\": { \"SingularDisplayName\": \"Application Gateway for Containers\" }\r\n ,\"microsoft.serviceshub/connectors\": { \"SingularDisplayName\": \"Services Hub Connector\" }\r\n ,\"microsoft.signalrservice/signalr\": { \"SingularDisplayName\": \"SignalR\" }\r\n ,\"microsoft.signalrservice/signalr/replicas\": { \"SingularDisplayName\": \"SignalR Replica\" }\r\n ,\"microsoft.signalrservice/webpubsub\": { \"SingularDisplayName\": \"Web PubSub Service\" }\r\n ,\"microsoft.signalrservice/webpubsub/replicas\": { \"SingularDisplayName\": \"Web PubSub Service Replica\" }\r\n ,\"microsoft.skytap/billingnodes\": { \"SingularDisplayName\": \"Microsoft.Skytap billing node\" }\r\n ,\"microsoft.skytap/interfaces\": { \"SingularDisplayName\": \"Microsoft.Skytap interface\" }\r\n ,\"microsoft.skytap/nodes\": { \"SingularDisplayName\": \"Microsoft.Skytap node\" }\r\n ,\"microsoft.softwareplan/hybridusebenefits\": { \"SingularDisplayName\": \"Microsoft.SoftwarePlan hybrid use benefit\" }\r\n ,\"microsoft.solutions/applicationdefinitions\": { \"SingularDisplayName\": \"Service catalog managed application definition\" }\r\n ,\"microsoft.solutions/applications\": { \"SingularDisplayName\": \"Managed application\" }\r\n ,\"microsoft.solutions/jitrequests\": { \"SingularDisplayName\": \"Microsoft.Solutions JIT request\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts\": { \"SingularDisplayName\": \"Landing zone account\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\r\n ,\"microsoft.sovereign/landingzoneaccounts/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\r\n ,\"microsoft.sovereign/landingzoneconfigurations\": { \"SingularDisplayName\": \"Landing Zone Configuration\" }\r\n ,\"microsoft.sovereign/landingzoneregistrations\": { \"SingularDisplayName\": \"Landing Zone Registration\" }\r\n ,\"microsoft.sovereign/transparencylogs\": { \"SingularDisplayName\": \"Transparency log\" }\r\n ,\"microsoft.sql/azuresql\": { \"SingularDisplayName\": \"Azure SQL resource\" }\r\n ,\"microsoft.sql/instancepools\": { \"SingularDisplayName\": \"Instance pool\" }\r\n ,\"microsoft.sql/managedinstances\": { \"SingularDisplayName\": \"SQL managed instance\" }\r\n ,\"microsoft.sql/managedinstances/databases\": { \"SingularDisplayName\": \"Managed database\" }\r\n ,\"microsoft.sql/servers\": { \"SingularDisplayName\": \"SQL server\" }\r\n ,\"microsoft.sql/servers/databases\": { \"SingularDisplayName\": \"SQL database\" }\r\n ,\"microsoft.sql/servers/elasticpools\": { \"SingularDisplayName\": \"SQL elastic pool\" }\r\n ,\"microsoft.sql/servers/jobagents\": { \"SingularDisplayName\": \"Elastic Job agent\" }\r\n ,\"microsoft.sql/virtualclusters\": { \"SingularDisplayName\": \"Virtual cluster\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine group\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachinegroups/availabilitygrouplisteners\": { \"SingularDisplayName\": \"Microsoft.SqlVirtualMachine sql virtual machine groups availability group listener\" }\r\n ,\"microsoft.sqlvirtualmachine/sqlvirtualmachines\": { \"SingularDisplayName\": \"SQL virtual machine\" }\r\n ,\"microsoft.standbypool/standbycontainergrouppools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pool\" }\r\n ,\"microsoft.standbypool/standbycontainergrouppools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby container group pools runtime view\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pool\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools/runtimeviews\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools runtime view\" }\r\n ,\"microsoft.standbypool/standbyvirtualmachinepools/standbyvirtualmachines\": { \"SingularDisplayName\": \"Microsoft.StandbyPool standby virtual machine pools standby virtual machine\" }\r\n ,\"microsoft.storage/storageaccounts\": { \"SingularDisplayName\": \"Storage account\" }\r\n ,\"microsoft.storageactions/storagetasks\": { \"SingularDisplayName\": \"Storage task - Azure Storage Actions\" }\r\n ,\"microsoft.storagecache/amlfilesystems\": { \"SingularDisplayName\": \"Azure Managed Lustre\" }\r\n ,\"microsoft.storagecache/caches\": { \"SingularDisplayName\": \"HPC cache\" }\r\n ,\"microsoft.storagediscovery/storagediscoveryworkspaces\": { \"SingularDisplayName\": \"Storage Discovery workspace\" }\r\n ,\"microsoft.storagehub/all\": { \"SingularDisplayName\": \"All resources\" }\r\n ,\"microsoft.storagehub/policycomplianceresources\": { \"SingularDisplayName\": \"Policy compliance\" }\r\n ,\"microsoft.storageinsights/storagecollectionrules\": { \"SingularDisplayName\": \"Microsoft.StorageInsights storage collection rule\" }\r\n ,\"microsoft.storagemover/storagemovers\": { \"SingularDisplayName\": \"Storage mover\" }\r\n ,\"microsoft.storagepool/diskpools\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pool\" }\r\n ,\"microsoft.storagepool/diskpools/iscsitargets\": { \"SingularDisplayName\": \"Microsoft.StoragePool disk pools iscsi target\" }\r\n ,\"microsoft.storagesync/storagesyncservices\": { \"SingularDisplayName\": \"Storage Sync Service\" }\r\n ,\"microsoft.storagetasks/storagetasks\": { \"SingularDisplayName\": \"Microsoft.StorageTasks storage task\" }\r\n ,\"microsoft.storsimple/managers\": { \"SingularDisplayName\": \"StorSimple device manager\" }\r\n ,\"microsoft.storsimple/managers/accesscontrolrecords\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers access control record\" }\r\n ,\"microsoft.storsimple/managers/bandwidthsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers bandwidth setting\" }\r\n ,\"microsoft.storsimple/managers/certificates\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers certificate\" }\r\n ,\"microsoft.storsimple/managers/devices\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers device\" }\r\n ,\"microsoft.storsimple/managers/devices/alertsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices alert setting\" }\r\n ,\"microsoft.storsimple/managers/devices/backuppolicies\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policy\" }\r\n ,\"microsoft.storsimple/managers/devices/backuppolicies/schedules\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup policies schedule\" }\r\n ,\"microsoft.storsimple/managers/devices/backupschedulegroups\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices backup schedule group\" }\r\n ,\"microsoft.storsimple/managers/devices/chapsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices chap setting\" }\r\n ,\"microsoft.storsimple/managers/devices/fileservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileserver\" }\r\n ,\"microsoft.storsimple/managers/devices/fileservers/shares\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices fileservers share\" }\r\n ,\"microsoft.storsimple/managers/devices/iscsiservers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiserver\" }\r\n ,\"microsoft.storsimple/managers/devices/iscsiservers/disks\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices iscsiservers disk\" }\r\n ,\"microsoft.storsimple/managers/devices/jobs\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices job\" }\r\n ,\"microsoft.storsimple/managers/devices/networksettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices network setting\" }\r\n ,\"microsoft.storsimple/managers/devices/securitysettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices security setting\" }\r\n ,\"microsoft.storsimple/managers/devices/timesettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices time setting\" }\r\n ,\"microsoft.storsimple/managers/devices/updatesummary\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices update summary\" }\r\n ,\"microsoft.storsimple/managers/devices/volumecontainers\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume container\" }\r\n ,\"microsoft.storsimple/managers/devices/volumecontainers/volumes\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers devices volume containers volume\" }\r\n ,\"microsoft.storsimple/managers/encryptionsettings\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers encryption setting\" }\r\n ,\"microsoft.storsimple/managers/extendedinformation\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers extended information\" }\r\n ,\"microsoft.storsimple/managers/storageaccountcredentials\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage account credential\" }\r\n ,\"microsoft.storsimple/managers/storagedomains\": { \"SingularDisplayName\": \"Microsoft.StorSimple managers storage domain\" }\r\n ,\"microsoft.streamanalytics/clusters\": { \"SingularDisplayName\": \"Stream Analytics cluster\" }\r\n ,\"microsoft.streamanalytics/streamingjobs\": { \"SingularDisplayName\": \"Stream Analytics job\" }\r\n ,\"microsoft.subscription/aliases\": { \"SingularDisplayName\": \"Microsoft.Subscription aliase\" }\r\n ,\"microsoft.subscription/changetenantrequest\": { \"SingularDisplayName\": \"Microsoft.Subscription change tenant request\" }\r\n ,\"microsoft.subscription/policies\": { \"SingularDisplayName\": \"Microsoft.Subscription policy\" }\r\n ,\"microsoft.subscription/subscriptiondefinitions\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription definition\" }\r\n ,\"microsoft.subscription/subscriptionoperations\": { \"SingularDisplayName\": \"Microsoft.Subscription subscription operation\" }\r\n ,\"microsoft.support/fileworkspaces\": { \"SingularDisplayName\": \"Microsoft.Support file workspace\" }\r\n ,\"microsoft.support/fileworkspaces/files\": { \"SingularDisplayName\": \"Microsoft.Support file workspaces file\" }\r\n ,\"microsoft.support/services\": { \"SingularDisplayName\": \"Microsoft.Support service\" }\r\n ,\"microsoft.support/services/problemclassifications\": { \"SingularDisplayName\": \"Microsoft.Support services problem classification\" }\r\n ,\"microsoft.support/supporttickets\": { \"SingularDisplayName\": \"Support Request\" }\r\n ,\"microsoft.sustainabilityservices/calculations\": { \"SingularDisplayName\": \"Project Sustainability Calculator\" }\r\n ,\"microsoft.symphony/instances\": { \"SingularDisplayName\": \"Microsoft.Symphony instance\" }\r\n ,\"microsoft.symphony/solutions\": { \"SingularDisplayName\": \"Microsoft.Symphony solution\" }\r\n ,\"microsoft.symphony/targets\": { \"SingularDisplayName\": \"Microsoft.Symphony target\" }\r\n ,\"microsoft.synapse/privatelinkhubs\": { \"SingularDisplayName\": \"Synapse private link hub\" }\r\n ,\"microsoft.synapse/workspaces\": { \"SingularDisplayName\": \"Synapse workspace\" }\r\n ,\"microsoft.synapse/workspaces/bigdatapools\": { \"SingularDisplayName\": \"Apache Spark pool\" }\r\n ,\"microsoft.synapse/workspaces/kustopools\": { \"SingularDisplayName\": \"Data Explorer pool\" }\r\n ,\"microsoft.synapse/workspaces/kustopools/databases\": { \"SingularDisplayName\": \"Data Explorer Database\" }\r\n ,\"microsoft.synapse/workspaces/scopepools\": { \"SingularDisplayName\": \"SCOPE pool\" }\r\n ,\"microsoft.synapse/workspaces/sqlpools\": { \"SingularDisplayName\": \"Dedicated SQL pool\" }\r\n ,\"microsoft.syntex/accounts\": { \"SingularDisplayName\": \"Microsoft.Syntex account\" }\r\n ,\"microsoft.syntex/documentprocessors\": { \"SingularDisplayName\": \"Microsoft.Syntex document processor\" }\r\n ,\"microsoft.test/healthdataaiservices\": { \"SingularDisplayName\": \"Azure Health Data and AI Services\" }\r\n ,\"microsoft.timeseriesinsights/environments\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environment\" }\r\n ,\"microsoft.timeseriesinsights/environments/accesspolicies\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments access policy\" }\r\n ,\"microsoft.timeseriesinsights/environments/eventsources\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments event source\" }\r\n ,\"microsoft.timeseriesinsights/environments/referencedatasets\": { \"SingularDisplayName\": \"Microsoft.TimeSeriesInsights environments reference data set\" }\r\n ,\"microsoft.toolchainorchestrator/activations\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator activation\" }\r\n ,\"microsoft.toolchainorchestrator/campaigns\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaign\" }\r\n ,\"microsoft.toolchainorchestrator/campaigns/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator campaigns version\" }\r\n ,\"microsoft.toolchainorchestrator/catalogs\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalog\" }\r\n ,\"microsoft.toolchainorchestrator/catalogs/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator catalogs version\" }\r\n ,\"microsoft.toolchainorchestrator/diagnostics\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator diagnostic\" }\r\n ,\"microsoft.toolchainorchestrator/instances\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instance\" }\r\n ,\"microsoft.toolchainorchestrator/instances/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator instances version\" }\r\n ,\"microsoft.toolchainorchestrator/solutions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solution\" }\r\n ,\"microsoft.toolchainorchestrator/solutions/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator solutions version\" }\r\n ,\"microsoft.toolchainorchestrator/targets\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator target\" }\r\n ,\"microsoft.toolchainorchestrator/targets/versions\": { \"SingularDisplayName\": \"Microsoft.ToolchainOrchestrator targets version\" }\r\n ,\"microsoft.updatemanager/updaterules\": { \"SingularDisplayName\": \"Update Rule\" }\r\n ,\"microsoft.usagebilling/accounts\": { \"SingularDisplayName\": \"Microsoft.UsageBilling account\" }\r\n ,\"microsoft.usagebilling/accounts/dataexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts data export\" }\r\n ,\"microsoft.usagebilling/accounts/inputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts input\" }\r\n ,\"microsoft.usagebilling/accounts/metricexports\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts metric export\" }\r\n ,\"microsoft.usagebilling/accounts/pav2outputs\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pav2output\" }\r\n ,\"microsoft.usagebilling/accounts/pipelines\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipeline\" }\r\n ,\"microsoft.usagebilling/accounts/pipelines/outputselectors\": { \"SingularDisplayName\": \"Microsoft.UsageBilling accounts pipelines output selector\" }\r\n ,\"microsoft.verifiedid/authorities\": { \"SingularDisplayName\": \"Microsoft.VerifiedId authority\" }\r\n ,\"microsoft.videoindexer/accounts\": { \"SingularDisplayName\": \"Azure AI Video Indexer\" }\r\n ,\"microsoft.virtualmachineimages/imagetemplates\": { \"SingularDisplayName\": \"Image template\" }\r\n ,\"microsoft.visualstudio/account\": { \"SingularDisplayName\": \"Azure DevOps organization\" }\r\n ,\"microsoft.vmware/resourcepools\": { \"SingularDisplayName\": \"Microsoft.VMware resource pool\" }\r\n ,\"microsoft.vmware/vcenters\": { \"SingularDisplayName\": \"Microsoft.VMware vcenter\" }\r\n ,\"microsoft.vmware/vcenters/inventoryitems\": { \"SingularDisplayName\": \"Microsoft.VMware vcenters inventory item\" }\r\n ,\"microsoft.vmware/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine\" }\r\n ,\"microsoft.vmware/virtualmachinetemplates\": { \"SingularDisplayName\": \"Microsoft.VMware virtual machine template\" }\r\n ,\"microsoft.vmware/virtualnetworks\": { \"SingularDisplayName\": \"Microsoft.VMware virtual network\" }\r\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudnodes\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud node\" }\r\n ,\"microsoft.vmwarecloudsimple/dedicatedcloudservices\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple dedicated cloud service\" }\r\n ,\"microsoft.vmwarecloudsimple/virtualmachines\": { \"SingularDisplayName\": \"Microsoft.VMwareCloudSimple virtual machine\" }\r\n ,\"microsoft.vnfmanager/devices\": { \"SingularDisplayName\": \"Microsoft.VnfManager device\" }\r\n ,\"microsoft.vnfmanager/vendors\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendor\" }\r\n ,\"microsoft.vnfmanager/vendors/skus\": { \"SingularDisplayName\": \"Microsoft.VnfManager vendors SKU\" }\r\n ,\"microsoft.vnfmanager/vnfs\": { \"SingularDisplayName\": \"Microsoft.VnfManager vnf\" }\r\n ,\"microsoft.voiceservices/communicationsgateways\": { \"SingularDisplayName\": \"Communications Gateway\" }\r\n ,\"microsoft.voiceservices/communicationsgateways/testlines\": { \"SingularDisplayName\": \"Communications Gateway Test Line\" }\r\n ,\"microsoft.vsonline/accounts\": { \"SingularDisplayName\": \"Microsoft.VSOnline account\" }\r\n ,\"microsoft.vsonline/plans\": { \"SingularDisplayName\": \"Visual Studio Online Plan\" }\r\n ,\"microsoft.web/certificates\": { \"SingularDisplayName\": \"Microsoft.Web certificate\" }\r\n ,\"microsoft.web/connectiongateways\": { \"SingularDisplayName\": \"App Service on-premises data gateway\" }\r\n ,\"microsoft.web/connections\": { \"SingularDisplayName\": \"App Service API connection\" }\r\n ,\"microsoft.web/containerapps\": { \"SingularDisplayName\": \"Microsoft.Web container app\" }\r\n ,\"microsoft.web/containerapps/revisions\": { \"SingularDisplayName\": \"Microsoft.Web container apps revision\" }\r\n ,\"microsoft.web/customapis\": { \"SingularDisplayName\": \"Logic apps custom connector\" }\r\n ,\"microsoft.web/deletedsites\": { \"SingularDisplayName\": \"Microsoft.Web deleted site\" }\r\n ,\"microsoft.web/hostingenvironments\": { \"SingularDisplayName\": \"App Service Environment\" }\r\n ,\"microsoft.web/ishostingenvironmentnameavailable\": { \"SingularDisplayName\": \"Microsoft.Web ishostingenvironmentnameavailable\" }\r\n ,\"microsoft.web/kubeenvironments\": { \"SingularDisplayName\": \"App Service Kubernetes Environment\" }\r\n ,\"microsoft.web/logicappstemplate\": { \"SingularDisplayName\": \"Logic Apps Template\" }\r\n ,\"microsoft.web/publishingusers\": { \"SingularDisplayName\": \"Microsoft.Web publishing user\" }\r\n ,\"microsoft.web/serverfarms\": { \"SingularDisplayName\": \"App Service plan\" }\r\n ,\"microsoft.web/sites\": { \"SingularDisplayName\": \"App Service web app\" }\r\n ,\"microsoft.web/sites/slots\": { \"SingularDisplayName\": \"App Service deployment slot\" }\r\n ,\"microsoft.web/sourcecontrols\": { \"SingularDisplayName\": \"Microsoft.Web sourcecontrol\" }\r\n ,\"microsoft.web/staticsites\": { \"SingularDisplayName\": \"Static Web App\" }\r\n ,\"microsoft.weightsandbiases/instances\": { \"SingularDisplayName\": \"Azure Native Weights & Biases Cloud Service\" }\r\n ,\"microsoft.whiteboxcadlprovider/whiteboxresources\": { \"SingularDisplayName\": \"Microsoft.WhiteBoxCadlProvider white box resource\" }\r\n ,\"microsoft.windows365/cloudpcdelegatedmsis\": { \"SingularDisplayName\": \"Microsoft.Windows365 cloud pc delegated msi\" }\r\n ,\"microsoft.windowsesu/multipleactivationkeys\": { \"SingularDisplayName\": \"Microsoft.WindowsESU multiple activation key\" }\r\n ,\"microsoft.windowsiot/deviceservices\": { \"SingularDisplayName\": \"Microsoft.WindowsIoT device service\" }\r\n ,\"microsoft.windowspushnotificationservices/registrations\": { \"SingularDisplayName\": \"Windows Push Notification Service\" }\r\n ,\"microsoft.workloadmonitor/monitors\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitor\" }\r\n ,\"microsoft.workloadmonitor/monitors/history\": { \"SingularDisplayName\": \"Microsoft.WorkloadMonitor monitors history\" }\r\n ,\"microsoft.workloads/configurationvalidationresults\": { \"SingularDisplayName\": \"Microsoft.Workloads configuration validation result\" }\r\n ,\"microsoft.workloads/connectors\": { \"SingularDisplayName\": \"Microsoft.Workloads connector\" }\r\n ,\"microsoft.workloads/connectors/acssbackups\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors acss backup\" }\r\n ,\"microsoft.workloads/connectors/amsinsights\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors ams insight\" }\r\n ,\"microsoft.workloads/connectors/sapvirtualinstancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads connectors sap virtual instance monitor\" }\r\n ,\"microsoft.workloads/epicvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for Epic solution\" }\r\n ,\"microsoft.workloads/insights\": { \"SingularDisplayName\": \"Microsoft.Workloads insight\" }\r\n ,\"microsoft.workloads/instancegroupmonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance group monitor\" }\r\n ,\"microsoft.workloads/instancehealthdefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definition\" }\r\n ,\"microsoft.workloads/instancehealthdefinitions/signaldefinitions\": { \"SingularDisplayName\": \"Microsoft.Workloads instance health definitions signal definition\" }\r\n ,\"microsoft.workloads/instancemonitors\": { \"SingularDisplayName\": \"Microsoft.Workloads instance monitor\" }\r\n ,\"microsoft.workloads/monitors\": { \"SingularDisplayName\": \"Azure Monitor for SAP solutions\" }\r\n ,\"microsoft.workloads/oraclevirtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instance\" }\r\n ,\"microsoft.workloads/oraclevirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads oracle virtual instances database instance\" }\r\n ,\"microsoft.workloads/phpworkloads\": { \"SingularDisplayName\": \"Microsoft.Workloads php workload\" }\r\n ,\"microsoft.workloads/phpworkloads/wordpressinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads php workloads wordpress instance\" }\r\n ,\"microsoft.workloads/sapdiscoverysites\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery site\" }\r\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instance\" }\r\n ,\"microsoft.workloads/sapdiscoverysites/sapinstances/serverinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads sap discovery sites sap instances server instance\" }\r\n ,\"microsoft.workloads/sapvirtualinstances\": { \"SingularDisplayName\": \"Virtual Instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/applicationinstances\": { \"SingularDisplayName\": \"App server instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/centralinstances\": { \"SingularDisplayName\": \"Central service instance for SAP solutions\" }\r\n ,\"microsoft.workloads/sapvirtualinstances/databaseinstances\": { \"SingularDisplayName\": \"Database for SAP solutions\" }\r\n ,\"microsoft.workloads/virtualinstances\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instance\" }\r\n ,\"microsoft.workloads/virtualinstances/components\": { \"SingularDisplayName\": \"Microsoft.Workloads virtual instances component\" }\r\n ,\"microsoft.workloads/workloadinstance\": { \"SingularDisplayName\": \"My Resource\" }\r\n ,\"microsoft.zerotrustsegmentation/segmentationmanagers\": { \"SingularDisplayName\": \"Segmentation Manager\" }\r\n ,\"mongodb.atlas/organizations\": { \"SingularDisplayName\": \"MongoDB Atlas Organization\" }\r\n ,\"neon.postgres/organizations\": { \"SingularDisplayName\": \"Neon Serverless Postgres Organization\" }\r\n ,\"newrelic.observability/monitors\": { \"SingularDisplayName\": \"New Relic\" }\r\n ,\"nginx.nginxplus/nginxdeployments\": { \"SingularDisplayName\": \"NGINXaaS\" }\r\n ,\"oracle.database/autonomousdatabases\": { \"SingularDisplayName\": \"Autonomous Database\" }\r\n ,\"oracle.database/basedb\": { \"SingularDisplayName\": \"Autonomous Database\" }\r\n ,\"oracle.database/cloudexadatainfrastructures\": { \"SingularDisplayName\": \"Oracle Exadata Infrastructure\" }\r\n ,\"oracle.database/cloudvmclusters\": { \"SingularDisplayName\": \"Oracle Exadata VM Cluster\" }\r\n ,\"oracle.database/exadbvmclusters\": { \"SingularDisplayName\": \"Oracle Exascale VM Cluster\" }\r\n ,\"oracle.database/exascaledbstoragevaults\": { \"SingularDisplayName\": \"Oracle Exascale DB Storage Vault\" }\r\n ,\"oracle.database/networkanchors\": { \"SingularDisplayName\": \"Network Anchor\" }\r\n ,\"oracle.database/oraclesubscriptions\": { \"SingularDisplayName\": \"OracleSubscription\" }\r\n ,\"oracle.database/resourceanchors\": { \"SingularDisplayName\": \"Resource Anchor\" }\r\n ,\"paloaltonetworks.cloudngfw/firewalls\": { \"SingularDisplayName\": \"Cloud NGFW by Palo Alto Networks\" }\r\n ,\"paloaltonetworks.cloudngfw/globalrulestacks\": { \"SingularDisplayName\": \"Global Rulestack\" }\r\n ,\"paloaltonetworks.cloudngfw/localrulestacks\": { \"SingularDisplayName\": \"Local Rulestack for Cloud NGFW by Palo Alto Networks\" }\r\n ,\"pinecone.vectordb/organizations\": { \"SingularDisplayName\": \"Azure Native Pinecone Cloud Service\" }\r\n ,\"purestorage.block/reservations\": { \"SingularDisplayName\": \"Azure Native Pure Storage Cloud Service\" }\r\n ,\"purestorage.block/storagepools\": { \"SingularDisplayName\": \"Storage pool\" }\r\n ,\"purestorage.block/storagepools/avsstoragecontainers\": { \"SingularDisplayName\": \"PureStorage.Block storage pools avs storage container\" }\r\n })[tolower(id)]\r\n}\r\n", + "$fxv#4": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData/Internal')\r\n_resource_type_5(id: string) {\r\n dynamic({\r\n \"qumulo.qaas/storages\": { \"SingularDisplayName\": \"Qumulo.QaaS storage\" }\r\n ,\"qumulo.storage/filesystems\": { \"SingularDisplayName\": \"Azure Native Qumulo Scalable File Service\" }\r\n ,\"solarwinds.observability/organizations\": { \"SingularDisplayName\": \"SolarWinds Observability\" }\r\n ,\"splitio.experimentation/experimentationworkspaces\": { \"SingularDisplayName\": \"Split Experimentation Workspace\" }\r\n ,\"wandisco.fusion/migrators\": { \"SingularDisplayName\": \"LiveData Migrator\" }\r\n ,\"wandisco.fusion/migrators/datatransferagents\": { \"SingularDisplayName\": \"Data Transfer Agent\" }\r\n ,\"wandisco.fusion/migrators/exclusiontemplates\": { \"SingularDisplayName\": \"Exclusion\" }\r\n ,\"wandisco.fusion/migrators/livedatamigrations\": { \"SingularDisplayName\": \"Migration\" }\r\n ,\"wandisco.fusion/migrators/metadatamigrations\": { \"SingularDisplayName\": \"Metadata Migration\" }\r\n ,\"wandisco.fusion/migrators/metadatatargets\": { \"SingularDisplayName\": \"Metadata Target\" }\r\n ,\"wandisco.fusion/migrators/pathmappings\": { \"SingularDisplayName\": \"Path Mapping\" }\r\n ,\"wandisco.fusion/migrators/targets\": { \"SingularDisplayName\": \"Target\" }\r\n ,\"wandisco.fusion/migrators/verifications\": { \"SingularDisplayName\": \"Verification\" }\r\n })[tolower(id)]\r\n}\r\n", + "$fxv#5": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n// resource_type\r\n.create-or-alter function \r\nwith (docstring = 'Return details about the specified ID.', folder = 'OpenData')\r\nresource_type(id: string) {\r\n coalesce(_resource_type_1(id), _resource_type_2(id), _resource_type_3(id), _resource_type_4(id), _resource_type_5(id))\r\n}\r\n", + "$fxv#6": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Common utility functions\r\n//\r\n// TIP: Use Ctrl+K,Ctrl+0 to collapse all regions in VS Code\r\n//======================================================================================================================\r\n\r\n\r\n//===| Date functions |=================================================================================================\r\n\r\n// monthstring\r\n.create-or-alter function \r\nwith (docstring = @'Returns the name of the month for the specified date (e.g. Jan or January)', folder =@'Common') \r\nmonthstring(['date']: datetime, length: int = 9)\r\n{\r\n substring(dynamic(['January','February','March','April','May','June','July','August','September','October','November','December'])[getmonth(['date']) - 1], 0, length)\r\n}\r\n\r\n// datestring\r\n.create-or-alter function \r\nwith (docstring = @'Converts 2 dates into a simple, user-friendly date range (e.g. Jan 1-Jan 3)', folder =@'Common') \r\ndatestring(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n let month = (d: datetime) { monthstring(d, 3) };\r\n let endDate = iff(end == datetime('0001-01-01'), start, end);\r\n let sameDate = startofday(start) == startofday(endDate);\r\n let sameMonth = startofmonth(start) == startofmonth(endDate);\r\n let sameYear = startofyear(start) == startofyear(endDate);\r\n let fullMonth = startofday(start) == startofmonth(start) and startofday(endDate) == startofday(endofmonth(endDate));\r\n let fullYear = startofday(start) == startofyear(start) and startofday(endDate) == startofday(endofyear(endDate));\r\n let currentYear = sameYear and startofyear(start) == startofyear(now());\r\n case(\r\n // Full year | yyyy (same year) / yyyy-yyyy (diff years)\r\n fullYear,\r\n strcat(getyear(start), iff(sameYear, '', strcat('-', getyear(endDate)))),\r\n // 1 full mo, same year | Mmm yyyy\r\n fullMonth and sameMonth and sameYear,\r\n strcat(month(start), ' ', getyear(start)),\r\n // 2+ full mo, same year | Mmm-Mmm (current year) / Mmm-Mmm yyyy (other year)\r\n fullMonth and sameYear,\r\n strcat(month(start), '-', month(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // Full mo, diff year | Mmm yyyy-Mmm yyyy\r\n fullMonth and not(sameYear),\r\n strcat(month(start), ' ', getyear(start), '-', month(endDate), ' ', getyear(endDate)),\r\n // Same date | Mmm d (current year) / Mmm d, yyyy (other year)\r\n sameDate,\r\n strcat(month(start), ' ', dayofmonth(start), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // 1 partial M, same Y | Mmm d-d (current year) / Mmm d-d, yyyy (other year)\r\n not(fullMonth) and sameMonth and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', dayofmonth(endDate), iff(currentYear, '', strcat(' ', getyear(endDate)))),\r\n // 2+ partial M, same Y | Mmm d-Mmm d (current year) / Mmm d-Mmm d, yyyy (other year)\r\n not(fullMonth) and not(sameMonth) and sameYear,\r\n strcat(month(start), ' ', dayofmonth(start), '-', month(endDate), ' ', dayofmonth(endDate), iff(currentYear, '', strcat(', ', getyear(endDate)))),\r\n // All other cases | Mmm d, yyyy-Mmm d, yyyy\r\n strcat(month(start), ' ', dayofmonth(start), ', ', getyear(start), '-', month(endDate), ' ', dayofmonth(endDate), ', ', getyear(endDate))\r\n )\r\n}\r\n\r\n// daterange\r\n.create-or-alter function \r\nwith (docstring = @'DEPRECATED: Please use datestring(); function will be removed on or after the Jan 2026 release', folder =@'Common') \r\ndaterange(start: datetime, end: datetime = datetime('0001-01-01'))\r\n{\r\n datestring(start, end)\r\n}\r\n\r\n// monthsago\r\n.create-or-alter function \r\nwith (docstring = 'DEPRECATED: Please use startofmonth(now(), -<# of months>); function will be removed on or after the Jan 2026 release', folder = 'Common')\r\nmonthsago(months: int)\r\n{\r\n datetime_add('month', -months, startofmonth(now()))\r\n}\r\n\r\n\r\n//===| Number functions |===============================================================================================\r\n// NOTE: Must be defined before string converters\r\n\r\n// delta\r\n.create-or-alter function \r\nwith (docstring = @'Compares 2 values and returns the percentage change from oldval to newval', folder =@'Common') \r\ndelta(oldval: double, newval: double)\r\n{\r\n (newval - todouble(oldval))/oldval\r\n}\r\n\r\n// percentOfTotal\r\n// NOTE: Must be before percent() function\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercentOfTotal(t: (Count: long), tot: long)\r\n{\r\n let total = todouble(tot);\r\n t \r\n | extend Percent = round(Count / total * 100, 3) \r\n | order by Count desc\r\n}\r\n\r\n// percent\r\n.create-or-alter function \r\nwith (docstring = @'Calculates the percentage of each record based on a required Count column', folder =@'Common') \r\npercent(t: (Count: long))\r\n{\r\n let total = todouble(toscalar(t | summarize sum(Count)));\r\n percentOfTotal(t, total)\r\n}\r\n\r\n// plusminus\r\n.create-or-alter function \r\nwith (docstring = 'Shows a +/- sign based on the direction of the number', folder = 'Common')\r\nplusminus(val: string)\r\n{\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, val, strcat('+', val))\r\n}\r\n\r\n// updown\r\n.create-or-alter function \r\nwith (docstring = 'Shows an up/down arrow based on the direction of the number', folder = 'Common')\r\nupdown(val: string)\r\n{\r\n // TODO: Handle 0\r\n let neg = substring(val, 0, 1) == '-';\r\n iff(neg, strcat('↓', substring(val, 1)), strcat('↑', val))\r\n}\r\n\r\n\r\n//===| String functions |===============================================================================================\r\n\r\n// percentstring\r\n// NOTE: Must be defined before deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a percentage and render as a string', folder = 'Common')\r\npercentstring(num: double, total: double = 1.0, places: int = 9)\r\n{\r\n let value = 1.0 * num / total * 100;\r\n strcat(case(\r\n places != 9, round(value, places),\r\n value < 10, round(value, 2),\r\n round(value, 1)\r\n ), '%')\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// arraystring\r\n.create-or-alter function \r\nwith (docstring = 'Convert an array to a comma-delimited string', folder = 'Common')\r\narraystring(arr: dynamic)\r\n{\r\n replace_string(replace_regex(replace_regex(replace_regex(replace_regex(replace_regex(\r\n tostring(arr)\r\n , @'^\\[\"', '')\r\n , @'\"\\]$', '')\r\n , @'^, ', '')\r\n , @', $', '')\r\n , @'^\\[]$', '')\r\n , '\",\"', ', ')\r\n}\r\n\r\n// deltastring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate a delta percentage and render as a string', folder = 'Common')\r\ndeltastring(oldval: double, newval: double, places: int = 1, useArrows: bool = false)\r\n{\r\n let d = delta(oldval, newval);\r\n strcat(case(useArrows and d > 0, '↑', useArrows and d < 0, '↓', d < 0, '-', ''), percentstring(abs(d), 1, places))\r\n}\r\n\r\n// diffstring\r\n.create-or-alter function \r\nwith (docstring = 'Calculate the difference and render as a string', folder = 'Common')\r\ndiffstring(oldval: double, newval: double, places: int = 1)\r\n{\r\n plusminus(round(newval - oldval, places))\r\n}\r\n\r\n// numberstring\r\n.create-or-alter function \r\nwith (docstring = 'Convert a number to a string', folder = 'Common')\r\nnumberstring(num: double, abbrev: bool = true)\r\n{\r\n replace_regex(case(\r\n num >= 10000000000000, strcat(round(1.0 * num / 1000000000000, 1), 'T'),\r\n num >= 1000000000000, strcat(round(1.0 * num / 1000000000000, 2), 'T'),\r\n num >= 10000000000, strcat(round(1.0 * num / 1000000000, 1), 'B'),\r\n num >= 1000000000, strcat(round(1.0 * num / 1000000000, 2), 'B'),\r\n num >= 10000000, strcat(round(1.0 * num / 1000000, 1), 'M'),\r\n num >= 1000000, strcat(round(1.0 * num / 1000000, 2), 'M'),\r\n num >= 10000, strcat(round(1.0 * num / 1000, 1), 'K'),\r\n // Kusto doesn't support back-refs yet -- num > 1000, replace_regex(tostring(num), @'(\\d)(?=(\\d{3})+\\.)', @'\\1,'), // See https://docs.microsoft.com/azure/data-explorer/kusto/query/re2-library\r\n num > 1000, replace_regex(tostring(num), @'([0-9]{3})$', @',\\1'), //num / 1000, ',', substring(tostring(num), 0) - (num / 1000 * 1000)),\r\n tostring(num)\r\n ), @'\\.0$', '')\r\n}\r\n\r\n\r\n//===| Other |==========================================================================================================\r\n\r\n// ifempty\r\n.create-or-alter function \r\nwith (docstring = 'Replaces an empty value with the specified default value', folder = 'Common')\r\nifempty(val: dynamic, defaultVal: dynamic)\r\n{\r\n iff(isempty(val), defaultVal, val)\r\n}\r\n", + "$fxv#7": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Settings |=======================================================================================================\r\n\r\n.create-merge table HubSettingsLog (\r\n version: string,\r\n scopes: dynamic,\r\n retention: dynamic\r\n)\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// HubSettings function\r\n.create-or-alter function\r\nwith (docstring='Gets the latest version of hub settings.', folder='Settings')\r\nHubSettings()\r\n{\r\n HubSettingsLog\r\n | extend timestamp = ingestion_time()\r\n | summarize arg_max(timestamp, *)\r\n}\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// HubScopes function\r\n.create-or-alter function\r\nwith (docstring='Gets the currently configured scopes.', folder='Settings')\r\nHubScopes()\r\n{\r\n HubSettings\r\n | project scopes\r\n | mv-expand scopes\r\n}\r\n\r\n\r\n//===| Open data |======================================================================================================\r\n\r\n// PricingUnits -- Create table if it doesn't exist\r\n.create-merge table PricingUnits ( ignore: string )\r\n\r\n// PricingUnits -- Remove all columns\r\n.alter table PricingUnits ( ignore: string )\r\n\r\n// PricingUnits -- Redefine all columns to change types\r\n.alter table PricingUnits (\r\n x_PricingUnitDescription: string,\r\n x_PricingBlockSize: real,\r\n PricingUnit: string\r\n)\r\n\r\n// Regions\r\n.create-merge table Regions(\r\n ResourceLocation: string,\r\n RegionId: string,\r\n RegionName: string\r\n)\r\n\r\n// ResourceTypes\r\n.create-merge table ResourceTypes(\r\n x_ResourceType: string,\r\n SingularDisplayName: string,\r\n PluralDisplayName: string,\r\n LowerSingularDisplayName: string,\r\n LowerPluralDisplayName: string,\r\n IsPreview: bool,\r\n Description: string,\r\n IconUri: string\r\n)\r\n\r\n// Services\r\n.create-merge table Services(\r\n x_ConsumedService: string,\r\n x_ResourceType: string,\r\n ServiceName: string,\r\n ServiceCategory: string,\r\n ServiceSubcategory: string,\r\n PublisherName: string,\r\n x_PublisherCategory: string,\r\n x_Environment: string,\r\n x_ServiceModel: string\r\n)\r\n\r\n//----------------------------------------------------------------------------------------------------------------------\r\n\r\n// parse_resourceid\r\n.create-or-alter function\r\nwith (docstring = 'Parses an Azure resource ID to extract resource attributes like the name, type, resource group, and subaccount ID.', folder = 'Common')\r\nparse_resourceid(resourceId: string) {\r\n let ResourceId = tolower(resourceId);\r\n // let ResourceId = tolower('/providers/Microsoft.BillingBenefits/savingsPlanOrders/2d2e284b-0638-427e-b8c6-1b874d4f17c8/sp/xxx');\r\n let SubAccountId = tostring(extract('/subscriptions/[^/]+', 1, ResourceId));\r\n let x_ResourceGroupName = tostring(extract('/resourcegroups/[^/]+', 1, ResourceId));\r\n let providerPath = iff(ResourceId !contains '/providers/', '', split(iff(ResourceId startswith '/subscriptions/', strcat('/providers/microsoft.resources/', ResourceId), ResourceId), '/providers/')[-1]);\r\n let x_ResourceProvider = iff(isempty(providerPath), '', split(providerPath, '/')[0]);\r\n let tmp_ResourceProviderPath = iff(isempty(providerPath), '', substring(providerPath, strlen(x_ResourceProvider) + 1));\r\n let segments = split(tmp_ResourceProviderPath, '/');\r\n let ResourceName = trim(@'/+', replace_string(strcat_array(array_iff(\r\n dynamic([false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true]),\r\n segments, dynamic([])), '/'), '//', '/'));\r\n let x_ResourceTypePath = trim(@'/+', replace_string(strcat_array(array_iff(\r\n dynamic([true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]),\r\n segments, dynamic([])), '/'), '//', '/'));\r\n let xRT = iff(isempty(x_ResourceProvider) or isempty(x_ResourceTypePath), '', strcat(x_ResourceProvider, '/', x_ResourceTypePath));\r\n // TODO: Remove ResourceType in 0.9\r\n bag_pack('ResourceId', ResourceId, 'ResourceName', ResourceName, 'ResourceType', xRT, 'SubAccountId', SubAccountId, 'x_ResourceGroupName', x_ResourceGroupName, 'x_ResourceProvider', x_ResourceProvider, 'x_ResourceType', xRT)\r\n}\r\n", + "$fxv#8": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| ActualCosts |====================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_raw table -- Create the table if it doesn't exist\r\n.create-merge table ActualCosts_raw ( ignore: string )\r\n\r\n// ActualCosts_raw table -- Remove all columns to allow changing column types\r\n.alter table ActualCosts_raw ( ignore: string )\r\n\r\n// ActualCosts_raw table -- Redefine all columns\r\n.alter table ActualCosts_raw (\r\n AccountName: string,\r\n AccountOwnerId: string,\r\n AdditionalInfo: string,\r\n AvailabilityZone: string,\r\n BillingAccountId: string, \r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n BillingPeriodEndDate: datetime,\r\n BillingPeriodStartDate: datetime,\r\n BillingProfileId: string,\r\n BillingProfileName: string,\r\n ChargeType: string,\r\n ConsumedService: string,\r\n CostCenter: string,\r\n Cost: real,\r\n Date: datetime,\r\n EffectivePrice: real,\r\n Frequency: string,\r\n InvoiceSection: string,\r\n InvoiceSectionId: string,\r\n IsAzureCreditEligible: bool,\r\n MeterCategory: string,\r\n MeterId: string,\r\n MeterName: string,\r\n MeterRegion: string,\r\n MeterSubCategory: string,\r\n OfferId: string,\r\n PartNumber: string,\r\n PlanName: string,\r\n Product: string,\r\n ProductOrderId: string,\r\n ProductOrderName: string,\r\n PublisherName: string,\r\n PublisherType: string,\r\n Quantity: real,\r\n ReservationId: string,\r\n ReservationName: string,\r\n ResourceGroup: string,\r\n ResourceId: string,\r\n ResourceLocation: string,\r\n ResourceName: string,\r\n ServiceFamily: string,\r\n ServiceInfo1: string,\r\n ServiceInfo2: string,\r\n SubscriptionId: string,\r\n SubscriptionName: string,\r\n Tags: string,\r\n Term: string,\r\n UnitOfMeasure: string,\r\n UnitPrice: real\r\n)\r\n\r\n// ActualCosts_raw ingestion mapping\r\n.create-or-alter table ActualCosts_raw ingestion parquet mapping \"ActualCosts_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\r\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\r\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\r\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\r\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\r\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\r\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\r\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\r\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\r\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\r\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\r\n]\r\n```\r\n\r\n// ActualCosts_raw retention policy (clear historical data)\r\n.alter-merge table ActualCosts_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// ActualCosts_raw retention policy (set the user-defined retention period)\r\n.alter-merge table ActualCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable ActualCosts_raw streaming ingestion (required for Fabric)\r\n.alter table ActualCosts_raw policy streamingingestion disable\r\n\r\n\r\n//===| AmortizedCosts |=================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_raw table -- Create the table if it doesn't exist\r\n.create-merge table AmortizedCosts_raw ( ignore: string )\r\n\r\n// AmortizedCosts_raw table -- Remove all columns to allow changing column types\r\n.alter table AmortizedCosts_raw ( ignore: string )\r\n\r\n// AmortizedCosts_raw table -- Redefine all columns\r\n.alter table AmortizedCosts_raw (\r\n AccountName: string,\r\n AccountOwnerId: string,\r\n AdditionalInfo: string,\r\n AvailabilityZone: string,\r\n BillingAccountId: string, \r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n BillingPeriodEndDate: datetime,\r\n BillingPeriodStartDate: datetime,\r\n BillingProfileId: string,\r\n BillingProfileName: string,\r\n ChargeType: string,\r\n ConsumedService: string,\r\n CostCenter: string,\r\n Cost: real,\r\n Date: datetime,\r\n EffectivePrice: real,\r\n Frequency: string,\r\n InvoiceSection: string,\r\n InvoiceSectionId: string,\r\n IsAzureCreditEligible: bool,\r\n MeterCategory: string,\r\n MeterId: string,\r\n MeterName: string,\r\n MeterRegion: string,\r\n MeterSubCategory: string,\r\n OfferId: string,\r\n PartNumber: string,\r\n PlanName: string,\r\n Product: string,\r\n ProductOrderId: string,\r\n ProductOrderName: string,\r\n PublisherName: string,\r\n PublisherType: string,\r\n Quantity: real,\r\n ReservationId: string,\r\n ReservationName: string,\r\n ResourceGroup: string,\r\n ResourceId: string,\r\n ResourceLocation: string,\r\n ResourceName: string,\r\n ServiceFamily: string,\r\n ServiceInfo1: string,\r\n ServiceInfo2: string,\r\n SubscriptionId: string,\r\n SubscriptionName: string,\r\n Tags: string,\r\n Term: string,\r\n UnitOfMeasure: string,\r\n UnitPrice: real\r\n)\r\n\r\n// AmortizedCosts_raw ingestion mapping\r\n.create-or-alter table AmortizedCosts_raw ingestion parquet mapping \"AmortizedCosts_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerId\", \"Properties\": { \"Field\": \"AccountOwnerId\" } },\r\n { \"Column\": \"AdditionalInfo\", \"Properties\": { \"Field\": \"AdditionalInfo\" } },\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEndDate\", \"Properties\": { \"Field\": \"BillingPeriodEndDate\" } },\r\n { \"Column\": \"BillingPeriodStartDate\", \"Properties\": { \"Field\": \"BillingPeriodStartDate\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"ChargeType\", \"Properties\": { \"Field\": \"ChargeType\" } },\r\n { \"Column\": \"ConsumedService\", \"Properties\": { \"Field\": \"ConsumedService\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Cost\", \"Properties\": { \"Field\": \"Cost\" } },\r\n { \"Column\": \"Date\", \"Properties\": { \"Field\": \"Date\" } },\r\n { \"Column\": \"EffectivePrice\", \"Properties\": { \"Field\": \"EffectivePrice\" } },\r\n { \"Column\": \"Frequency\", \"Properties\": { \"Field\": \"Frequency\" } },\r\n { \"Column\": \"InvoiceSection\", \"Properties\": { \"Field\": \"InvoiceSection\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"IsAzureCreditEligible\", \"Properties\": { \"Field\": \"IsAzureCreditEligible\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"OfferId\", \"Properties\": { \"Field\": \"OfferId\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PlanName\", \"Properties\": { \"Field\": \"PlanName\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductOrderId\", \"Properties\": { \"Field\": \"ProductOrderId\" } },\r\n { \"Column\": \"ProductOrderName\", \"Properties\": { \"Field\": \"ProductOrderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"PublisherType\", \"Properties\": { \"Field\": \"PublisherType\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationName\", \"Properties\": { \"Field\": \"ReservationName\" } },\r\n { \"Column\": \"ResourceGroup\", \"Properties\": { \"Field\": \"ResourceGroup\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceLocation\", \"Properties\": { \"Field\": \"ResourceLocation\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"ServiceInfo1\", \"Properties\": { \"Field\": \"ServiceInfo1\" } },\r\n { \"Column\": \"ServiceInfo2\", \"Properties\": { \"Field\": \"ServiceInfo2\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"SubscriptionName\", \"Properties\": { \"Field\": \"SubscriptionName\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } }\r\n]\r\n```\r\n\r\n// AmortizedCosts_raw retention policy (clear historical data)\r\n.alter-merge table AmortizedCosts_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// AmortizedCosts_raw retention policy (set the user-defined retention period)\r\n.alter-merge table AmortizedCosts_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable AmortizedCosts_raw streaming ingestion (required for Fabric)\r\n.alter table AmortizedCosts_raw policy streamingingestion disable\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_raw table -- Create the table if it doesn't exist\r\n.create-merge table CommitmentDiscountUsage_raw ( ignore: string )\r\n\r\n// CommitmentDiscountUsage_raw table -- Remove all columns to allow changing column types\r\n.alter table CommitmentDiscountUsage_raw ( ignore: string )\r\n\r\n// CommitmentDiscountUsage_raw table -- Redefine all columns\r\n.alter table CommitmentDiscountUsage_raw (\r\n InstanceFlexibilityGroup: string,\r\n InstanceFlexibilityRatio: real,\r\n InstanceId: string,\r\n Kind: string,\r\n ReservationId: string,\r\n ReservationOrderId: string,\r\n ReservedHours: real,\r\n SkuName: string,\r\n TotalReservedQuantity: real,\r\n UsageDate: datetime,\r\n UsedHours: real,\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// CommitmentDiscountUsage_raw ingestion mapping\r\n.create-or-alter table CommitmentDiscountUsage_raw ingestion parquet mapping \"CommitmentDiscountUsage_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\r\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\r\n { \"Column\": \"InstanceId\", \"Properties\": { \"Field\": \"InstanceId\" } },\r\n { \"Column\": \"Kind\", \"Properties\": { \"Field\": \"Kind\" } },\r\n { \"Column\": \"ReservationId\", \"Properties\": { \"Field\": \"ReservationId\" } },\r\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\r\n { \"Column\": \"ReservedHours\", \"Properties\": { \"Field\": \"ReservedHours\" } },\r\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\r\n { \"Column\": \"TotalReservedQuantity\", \"Properties\": { \"Field\": \"TotalReservedQuantity\" } },\r\n { \"Column\": \"UsageDate\", \"Properties\": { \"Field\": \"UsageDate\" } },\r\n { \"Column\": \"UsedHours\", \"Properties\": { \"Field\": \"UsedHours\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// CommitmentDiscountUsage_raw retention policy (clear historical data)\r\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// CommitmentDiscountUsage_raw retention policy (set the user-defined retention period)\r\n.alter-merge table CommitmentDiscountUsage_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable CommitmentDiscountUsage_raw streaming ingestion (required for Fabric)\r\n.alter table CommitmentDiscountUsage_raw policy streamingingestion disable\r\n\r\n\r\n//===| Costs |==========================================================================================================\r\n// Supported versions:\r\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n// - Tencent: 1.0 -- See https://www.tencentcloud.com/document/product/555/67495 / https://www.tencentcloud.com/document/product/555/67496\r\n// - Alibaba: 1.0 -- See https://www.alibabacloud.com/help/en/user-center/user-guide/export-alibaba-cloud-standard-billing-focus\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_raw table -- Create the table if it doesn't exist\r\n.create-merge table Costs_raw ( ignore: string )\r\n\r\n// Costs_raw table -- Remove all columns to allow changing column types\r\n.alter table Costs_raw ( ignore: string )\r\n\r\n// Costs_raw table -- Redefine all columns\r\n.alter table Costs_raw (\r\n AvailabilityZone: string, // FOCUS 0.5+\r\n BilledCost: real, // FOCUS 0.5+\r\n BillingAccountId: string, // FOCUS 0.5+\r\n BillingAccountName: string, // FOCUS 0.5+\r\n BillingAccountType: string, // Azure 1.0-preview(v1)+\r\n BillingCurrency: string, // FOCUS 0.5+\r\n BillingPeriodEnd: datetime, // FOCUS 0.5+\r\n BillingPeriodStart: datetime, // FOCUS 0.5+\r\n CapacityReservationId: string, // FOCUS 1.1+\r\n CapacityReservationStatus: string, // FOCUS 1.1+\r\n ChargeCategory: string, // FOCUS 1.0-preview+\r\n ChargeClass: string, // FOCUS 1.0+\r\n ChargeDescription: string, // FOCUS 1.0+\r\n ChargeFrequency: string, // FOCUS 1.0+\r\n ChargePeriodEnd: datetime, // FOCUS 0.5+\r\n ChargePeriodStart: datetime, // FOCUS 0.5+\r\n ChargeSubcategory: string, // FOCUS 1.0-preview only\r\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountId: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountName: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountQuantity: real, // FOCUS 1.1+\r\n CommitmentDiscountStatus: string, // FOCUS 1.0+\r\n CommitmentDiscountType: string, // FOCUS 1.0-preview+\r\n CommitmentDiscountUnit: string, // FOCUS 1.1+\r\n ConsumedQuantity: real, // FOCUS 1.0+\r\n ConsumedUnit: string, // FOCUS 1.0+\r\n ContractedCost: real, // FOCUS 1.0+\r\n ContractedUnitPrice: real, // FOCUS 1.0+\r\n EffectiveCost: real, // FOCUS 1.0-preview+\r\n InvoiceId: string, // FOCUS 1.2+\r\n InvoiceIssuerName: string, // FOCUS 0.5+\r\n ListCost: real, // FOCUS 1.0-preview+\r\n ListUnitPrice: real, // FOCUS 1.0-preview+\r\n PricingCategory: string, // FOCUS 1.0-preview+\r\n PricingCurrency: string, // FOCUS 1.2+\r\n PricingQuantity: real, // FOCUS 1.0-preview+\r\n PricingUnit: string, // FOCUS 1.0-preview+\r\n ProviderName: string, // FOCUS 0.5+\r\n PublisherName: string, // FOCUS 0.5+\r\n Region: string, // FOCUS 0.5-1.0-preview (deprecated)\r\n RegionId: string, // FOCUS 1.0+\r\n RegionName: string, // FOCUS 1.0+\r\n ResourceId: string, // FOCUS 0.5+\r\n ResourceName: string, // FOCUS 0.5+\r\n ResourceType: string, // FOCUS 1.0-preview+\r\n ServiceCategory: string, // FOCUS 0.5+\r\n ServiceName: string, // FOCUS 0.5+\r\n ServiceSubcategory: string, // FOCUS 1.1+\r\n SkuId: string, // FOCUS 1.0-preview+\r\n SkuMeter: string, // FOCUS 1.1+\r\n SkuPriceDetails: string, // FOCUS 1.1+\r\n SkuPriceId: string, // FOCUS 1.0-preview+\r\n SubAccountId: string, // FOCUS 0.5+\r\n SubAccountName: string, // FOCUS 0.5+\r\n SubAccountType: string, // Azure 1.0-preview(v1)+\r\n Tags: string, // FOCUS 1.0-preview+\r\n UsageAmount: real, // GCP Jan 2024 -- Removed Mar 2024 (UsageQuantity)\r\n UsageQuantity: real, // FOCUS 1.0-preview only\r\n UsageUnit: string, // FOCUS 1.0-preview only\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_AmortizationClass: string, // Azure 1.2-preview+\r\n x_BilledCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: real, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingItemCode: string, // Alibaba 1.0+\r\n x_BillingItemName: string, // Alibaba 1.0+\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_CommodityCode: string, // Alibaba 1.0+\r\n x_CommodityName: string, // Alibaba 1.0+\r\n x_ComponentName: string, // Tencent 1.0+\r\n x_ComponentType: string, // Tencent 1.0+\r\n x_ContractedCostInUsd: real, // Azure 1.0+\r\n x_Cost: real, // GCP Jan 2024 -- Removed Jun 2024 (ContractedCost)\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: string, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_CostType: string, // GCP Jan 2024\r\n x_Credits: string, // GCP Jan 2024\r\n x_CurrencyConversionRate: real, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: string, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: real, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024 / Tencent 1.0+\r\n x_InstanceID: string, // Alibaba 1.0+\r\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: real, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_OnDemandCost: real, // Azure 1.0-preview(v1) only\r\n x_OnDemandCostInUsd: real, // Azure 1.0-preview(v1) only\r\n x_OnDemandUnitPrice: real, // Azure 1.0-preview(v1) only\r\n x_Operation: string, // AWS 1.0\r\n x_OwnerAccountID: string, // Tencent 1.0+\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: real, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency: string, // Azure 1.0-preview(v1)-1.0r2\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServiceModel: string, // Azure 1.2-preview+\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: string, // Azure 1.0-preview(v1)-1.2-preview\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName: string, // Azure 1.0-preview(v1)-1.0r2\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuPlanName: string, // Azure 1.2-preview+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string, // Hubs v1_0+\r\n x_SubproductName: string, // Tencent 1.0+ // cSpell:ignore Subproduct\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Costs_raw ingestion mapping\r\n.create-or-alter table Costs_raw ingestion parquet mapping \"Costs_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AvailabilityZone\", \"Properties\": { \"Field\": \"AvailabilityZone\" } },\r\n { \"Column\": \"BilledCost\", \"Properties\": { \"Field\": \"BilledCost\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingAccountType\", \"Properties\": { \"Field\": \"BillingAccountType\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingPeriodEnd\", \"Properties\": { \"Field\": \"BillingPeriodEnd\" } },\r\n { \"Column\": \"BillingPeriodStart\", \"Properties\": { \"Field\": \"BillingPeriodStart\" } },\r\n { \"Column\": \"CapacityReservationId\", \"Properties\": { \"Field\": \"CapacityReservationId\" } },\r\n { \"Column\": \"CapacityReservationStatus\", \"Properties\": { \"Field\": \"CapacityReservationStatus\" } },\r\n { \"Column\": \"ChargeCategory\", \"Properties\": { \"Field\": \"ChargeCategory\" } },\r\n { \"Column\": \"ChargeClass\", \"Properties\": { \"Field\": \"ChargeClass\" } },\r\n { \"Column\": \"ChargeDescription\", \"Properties\": { \"Field\": \"ChargeDescription\" } },\r\n { \"Column\": \"ChargeFrequency\", \"Properties\": { \"Field\": \"ChargeFrequency\" } },\r\n { \"Column\": \"ChargePeriodEnd\", \"Properties\": { \"Field\": \"ChargePeriodEnd\" } },\r\n { \"Column\": \"ChargePeriodStart\", \"Properties\": { \"Field\": \"ChargePeriodStart\" } },\r\n { \"Column\": \"ChargeSubcategory\", \"Properties\": { \"Field\": \"ChargeSubcategory\" } },\r\n { \"Column\": \"CommitmentDiscountCategory\", \"Properties\": { \"Field\": \"CommitmentDiscountCategory\" } },\r\n { \"Column\": \"CommitmentDiscountId\", \"Properties\": { \"Field\": \"CommitmentDiscountId\" } },\r\n { \"Column\": \"CommitmentDiscountName\", \"Properties\": { \"Field\": \"CommitmentDiscountName\" } },\r\n { \"Column\": \"CommitmentDiscountQuantity\", \"Properties\": { \"Field\": \"CommitmentDiscountQuantity\" } },\r\n { \"Column\": \"CommitmentDiscountStatus\", \"Properties\": { \"Field\": \"CommitmentDiscountStatus\" } },\r\n { \"Column\": \"CommitmentDiscountType\", \"Properties\": { \"Field\": \"CommitmentDiscountType\" } },\r\n { \"Column\": \"CommitmentDiscountUnit\", \"Properties\": { \"Field\": \"CommitmentDiscountUnit\" } },\r\n { \"Column\": \"ConsumedQuantity\", \"Properties\": { \"Field\": \"ConsumedQuantity\" } },\r\n { \"Column\": \"ConsumedUnit\", \"Properties\": { \"Field\": \"ConsumedUnit\" } },\r\n { \"Column\": \"ContractedCost\", \"Properties\": { \"Field\": \"ContractedCost\" } },\r\n { \"Column\": \"ContractedUnitPrice\", \"Properties\": { \"Field\": \"ContractedUnitPrice\" } },\r\n { \"Column\": \"EffectiveCost\", \"Properties\": { \"Field\": \"EffectiveCost\" } },\r\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\r\n { \"Column\": \"InvoiceIssuerName\", \"Properties\": { \"Field\": \"InvoiceIssuerName\" } },\r\n { \"Column\": \"ListCost\", \"Properties\": { \"Field\": \"ListCost\" } },\r\n { \"Column\": \"ListUnitPrice\", \"Properties\": { \"Field\": \"ListUnitPrice\" } },\r\n { \"Column\": \"PricingCategory\", \"Properties\": { \"Field\": \"PricingCategory\" } },\r\n { \"Column\": \"PricingCurrency\", \"Properties\": { \"Field\": \"PricingCurrency\" } },\r\n { \"Column\": \"PricingQuantity\", \"Properties\": { \"Field\": \"PricingQuantity\" } },\r\n { \"Column\": \"PricingUnit\", \"Properties\": { \"Field\": \"PricingUnit\" } },\r\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\r\n { \"Column\": \"PublisherName\", \"Properties\": { \"Field\": \"PublisherName\" } },\r\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\r\n { \"Column\": \"RegionId\", \"Properties\": { \"Field\": \"RegionId\" } },\r\n { \"Column\": \"RegionName\", \"Properties\": { \"Field\": \"RegionName\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\r\n { \"Column\": \"ServiceCategory\", \"Properties\": { \"Field\": \"ServiceCategory\" } },\r\n { \"Column\": \"ServiceName\", \"Properties\": { \"Field\": \"ServiceName\" } },\r\n { \"Column\": \"ServiceSubcategory\", \"Properties\": { \"Field\": \"ServiceSubcategory\" } },\r\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\r\n { \"Column\": \"SkuMeter\", \"Properties\": { \"Field\": \"SkuMeter\" } },\r\n { \"Column\": \"SkuPriceDetails\", \"Properties\": { \"Field\": \"SkuPriceDetails\" } },\r\n { \"Column\": \"SkuPriceId\", \"Properties\": { \"Field\": \"SkuPriceId\" } },\r\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\r\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\r\n { \"Column\": \"SubAccountType\", \"Properties\": { \"Field\": \"SubAccountType\" } },\r\n { \"Column\": \"Tags\", \"Properties\": { \"Field\": \"Tags\" } },\r\n { \"Column\": \"UsageAmount\", \"Properties\": { \"Field\": \"UsageAmount\" } },\r\n { \"Column\": \"UsageQuantity\", \"Properties\": { \"Field\": \"UsageQuantity\" } },\r\n { \"Column\": \"UsageUnit\", \"Properties\": { \"Field\": \"UsageUnit\" } },\r\n { \"Column\": \"x_AccountId\", \"Properties\": { \"Field\": \"x_AccountId\" } },\r\n { \"Column\": \"x_AccountName\", \"Properties\": { \"Field\": \"x_AccountName\" } },\r\n { \"Column\": \"x_AccountOwnerId\", \"Properties\": { \"Field\": \"x_AccountOwnerId\" } },\r\n { \"Column\": \"x_AmortizationClass\", \"Properties\": { \"Field\": \"x_AmortizationClass\" } },\r\n { \"Column\": \"x_BilledCostInUsd\", \"Properties\": { \"Field\": \"x_BilledCostInUsd\" } },\r\n { \"Column\": \"x_BilledUnitPrice\", \"Properties\": { \"Field\": \"x_BilledUnitPrice\" } },\r\n { \"Column\": \"x_BillingAccountId\", \"Properties\": { \"Field\": \"x_BillingAccountId\" } },\r\n { \"Column\": \"x_BillingAccountName\", \"Properties\": { \"Field\": \"x_BillingAccountName\" } },\r\n { \"Column\": \"x_BillingExchangeRate\", \"Properties\": { \"Field\": \"x_BillingExchangeRate\" } },\r\n { \"Column\": \"x_BillingExchangeRateDate\", \"Properties\": { \"Field\": \"x_BillingExchangeRateDate\" } },\r\n { \"Column\": \"x_BillingItemCode\", \"Properties\": { \"Field\": \"x_BillingItemCode\" } },\r\n { \"Column\": \"x_BillingItemName\", \"Properties\": { \"Field\": \"x_BillingItemName\" } },\r\n { \"Column\": \"x_BillingProfileId\", \"Properties\": { \"Field\": \"x_BillingProfileId\" } },\r\n { \"Column\": \"x_BillingProfileName\", \"Properties\": { \"Field\": \"x_BillingProfileName\" } },\r\n { \"Column\": \"x_ChargeId\", \"Properties\": { \"Field\": \"x_ChargeId\" } },\r\n { \"Column\": \"x_ContractedCostInUsd\", \"Properties\": { \"Field\": \"x_ContractedCostInUsd\" } },\r\n { \"Column\": \"x_CommodityCode\", \"Properties\": { \"Field\": \"x_CommodityCode\" } },\r\n { \"Column\": \"x_CommodityName\", \"Properties\": { \"Field\": \"x_CommodityName\" } },\r\n { \"Column\": \"x_ComponentName\", \"Properties\": { \"Field\": \"x_ComponentName\" } },\r\n { \"Column\": \"x_ComponentType\", \"Properties\": { \"Field\": \"x_ComponentType\" } },\r\n { \"Column\": \"x_Cost\", \"Properties\": { \"Field\": \"x_Cost\" } },\r\n { \"Column\": \"x_CostAllocationRuleName\", \"Properties\": { \"Field\": \"x_CostAllocationRuleName\" } },\r\n { \"Column\": \"x_CostCategories\", \"Properties\": { \"Field\": \"x_CostCategories\" } },\r\n { \"Column\": \"x_CostCenter\", \"Properties\": { \"Field\": \"x_CostCenter\" } },\r\n { \"Column\": \"x_Credits\", \"Properties\": { \"Field\": \"x_Credits\" } },\r\n { \"Column\": \"x_CostType\", \"Properties\": { \"Field\": \"x_CostType\" } },\r\n { \"Column\": \"x_CurrencyConversionRate\", \"Properties\": { \"Field\": \"x_CurrencyConversionRate\" } },\r\n { \"Column\": \"x_CustomerId\", \"Properties\": { \"Field\": \"x_CustomerId\" } },\r\n { \"Column\": \"x_CustomerName\", \"Properties\": { \"Field\": \"x_CustomerName\" } },\r\n { \"Column\": \"x_Discount\", \"Properties\": { \"Field\": \"x_Discount\" } },\r\n { \"Column\": \"x_EffectiveCostInUsd\", \"Properties\": { \"Field\": \"x_EffectiveCostInUsd\" } },\r\n { \"Column\": \"x_EffectiveUnitPrice\", \"Properties\": { \"Field\": \"x_EffectiveUnitPrice\" } },\r\n { \"Column\": \"x_ExportTime\", \"Properties\": { \"Field\": \"x_ExportTime\" } },\r\n { \"Column\": \"x_InstanceID\", \"Properties\": { \"Field\": \"x_InstanceID\" } },\r\n { \"Column\": \"x_InvoiceId\", \"Properties\": { \"Field\": \"x_InvoiceId\" } },\r\n { \"Column\": \"x_InvoiceIssuerId\", \"Properties\": { \"Field\": \"x_InvoiceIssuerId\" } },\r\n { \"Column\": \"x_InvoiceSectionId\", \"Properties\": { \"Field\": \"x_InvoiceSectionId\" } },\r\n { \"Column\": \"x_InvoiceSectionName\", \"Properties\": { \"Field\": \"x_InvoiceSectionName\" } },\r\n { \"Column\": \"x_ListCostInUsd\", \"Properties\": { \"Field\": \"x_ListCostInUsd\" } },\r\n { \"Column\": \"x_Location\", \"Properties\": { \"Field\": \"x_Location\" } },\r\n { \"Column\": \"x_OnDemandCost\", \"Properties\": { \"Field\": \"x_OnDemandCost\" } },\r\n { \"Column\": \"x_OnDemandCostInUsd\", \"Properties\": { \"Field\": \"x_OnDemandCostInUsd\" } },\r\n { \"Column\": \"x_OnDemandUnitPrice\", \"Properties\": { \"Field\": \"x_OnDemandUnitPrice\" } },\r\n { \"Column\": \"x_Operation\", \"Properties\": { \"Field\": \"x_Operation\" } },\r\n { \"Column\": \"x_OwnerAccountID\", \"Properties\": { \"Field\": \"x_OwnerAccountID\" } },\r\n { \"Column\": \"x_PartnerCreditApplied\", \"Properties\": { \"Field\": \"x_PartnerCreditApplied\" } },\r\n { \"Column\": \"x_PartnerCreditRate\", \"Properties\": { \"Field\": \"x_PartnerCreditRate\" } },\r\n { \"Column\": \"x_PricingBlockSize\", \"Properties\": { \"Field\": \"x_PricingBlockSize\" } },\r\n { \"Column\": \"x_PricingCurrency\", \"Properties\": { \"Field\": \"x_PricingCurrency\" } },\r\n { \"Column\": \"x_PricingSubcategory\", \"Properties\": { \"Field\": \"x_PricingSubcategory\" } },\r\n { \"Column\": \"x_PricingUnitDescription\", \"Properties\": { \"Field\": \"x_PricingUnitDescription\" } },\r\n { \"Column\": \"x_Project\", \"Properties\": { \"Field\": \"x_Project\" } },\r\n { \"Column\": \"x_PublisherCategory\", \"Properties\": { \"Field\": \"x_PublisherCategory\" } },\r\n { \"Column\": \"x_PublisherId\", \"Properties\": { \"Field\": \"x_PublisherId\" } },\r\n { \"Column\": \"x_ResellerId\", \"Properties\": { \"Field\": \"x_ResellerId\" } },\r\n { \"Column\": \"x_ResellerName\", \"Properties\": { \"Field\": \"x_ResellerName\" } },\r\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\r\n { \"Column\": \"x_ResourceType\", \"Properties\": { \"Field\": \"x_ResourceType\" } },\r\n { \"Column\": \"x_ServiceCode\", \"Properties\": { \"Field\": \"x_ServiceCode\" } },\r\n { \"Column\": \"x_ServiceId\", \"Properties\": { \"Field\": \"x_ServiceId\" } },\r\n { \"Column\": \"x_ServiceModel\", \"Properties\": { \"Field\": \"x_ServiceModel\" } },\r\n { \"Column\": \"x_ServicePeriodEnd\", \"Properties\": { \"Field\": \"x_ServicePeriodEnd\" } },\r\n { \"Column\": \"x_ServicePeriodStart\", \"Properties\": { \"Field\": \"x_ServicePeriodStart\" } },\r\n { \"Column\": \"x_SkuDescription\", \"Properties\": { \"Field\": \"x_SkuDescription\" } },\r\n { \"Column\": \"x_SkuDetails\", \"Properties\": { \"Field\": \"x_SkuDetails\" } },\r\n { \"Column\": \"x_SkuIsCreditEligible\", \"Properties\": { \"Field\": \"x_SkuIsCreditEligible\" } },\r\n { \"Column\": \"x_SkuMeterCategory\", \"Properties\": { \"Field\": \"x_SkuMeterCategory\" } },\r\n { \"Column\": \"x_SkuMeterId\", \"Properties\": { \"Field\": \"x_SkuMeterId\" } },\r\n { \"Column\": \"x_SkuMeterName\", \"Properties\": { \"Field\": \"x_SkuMeterName\" } },\r\n { \"Column\": \"x_SkuMeterSubcategory\", \"Properties\": { \"Field\": \"x_SkuMeterSubcategory\" } },\r\n { \"Column\": \"x_SkuOfferId\", \"Properties\": { \"Field\": \"x_SkuOfferId\" } },\r\n { \"Column\": \"x_SkuOrderId\", \"Properties\": { \"Field\": \"x_SkuOrderId\" } },\r\n { \"Column\": \"x_SkuOrderName\", \"Properties\": { \"Field\": \"x_SkuOrderName\" } },\r\n { \"Column\": \"x_SkuPartNumber\", \"Properties\": { \"Field\": \"x_SkuPartNumber\" } },\r\n { \"Column\": \"x_SkuPlanName\", \"Properties\": { \"Field\": \"x_SkuPlanName\" } },\r\n { \"Column\": \"x_SkuRegion\", \"Properties\": { \"Field\": \"x_SkuRegion\" } },\r\n { \"Column\": \"x_SkuServiceFamily\", \"Properties\": { \"Field\": \"x_SkuServiceFamily\" } },\r\n { \"Column\": \"x_SkuTerm\", \"Properties\": { \"Field\": \"x_SkuTerm\" } },\r\n { \"Column\": \"x_SkuTier\", \"Properties\": { \"Field\": \"x_SkuTier\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } },\r\n { \"Column\": \"x_SubproductName\", \"Properties\": { \"Field\": \"x_SubproductName\" } },\r\n { \"Column\": \"x_UsageType\", \"Properties\": { \"Field\": \"x_UsageType\" } }\r\n]\r\n```\r\n\r\n// Costs_raw retention policy (clear historical data)\r\n.alter-merge table Costs_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Costs_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Costs_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Costs_raw streaming ingestion (required for Fabric)\r\n.alter table Costs_raw policy streamingingestion disable\r\n\r\n\r\n//===| Prices |=========================================================================================================\r\n// NOTE: Must be before cost details.\r\n//\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_raw table -- Create the table if it doesn't exist\r\n.create-merge table Prices_raw ( ignore: string )\r\n\r\n// Prices_raw table -- Remove all columns to allow changing column types\r\n.alter table Prices_raw ( ignore: string )\r\n\r\n// Prices_raw table -- Redefine all columns\r\n.alter table Prices_raw (\r\n BasePrice: real, // Azure EA + MCA\r\n BillingAccountId: string, // Azure MCA\r\n BillingAccountName: string, // Azure MCA\r\n BillingCurrency: string, // Azure MCA\r\n BillingProfileId: string, // Azure MCA\r\n BillingProfileName: string, // Azure MCA\r\n Currency: string, // Azure MCA\r\n CurrencyCode: string, // Azure EA\r\n EffectiveEndDate: datetime, // Azure MCA\r\n EffectiveStartDate: datetime, // Azure EA + MCA\r\n EnrollmentNumber: string, // Azure EA\r\n IncludedQuantity: real, // Azure EA\r\n MarketPrice: real, // Azure EA + MCA\r\n MeterCategory: string, // Azure EA + MCA\r\n MeterId: string, // Azure MCA\r\n MeterID: string, // Azure EA\r\n MeterName: string, // Azure EA + MCA\r\n MeterRegion: string, // Azure EA + MCA\r\n MeterSubCategory: string, // Azure EA + MCA\r\n MeterType: string, // Azure EA + MCA\r\n OfferID: string, // Azure EA\r\n PartNumber: string, // Azure EA\r\n PriceType: string, // Azure EA + MCA\r\n Product: string, // Azure EA + MCA\r\n ProductId: string, // Azure MCA\r\n ProductID: string, // Azure EA\r\n ServiceFamily: string, // Azure EA + MCA\r\n SkuId: string, // Azure MCA\r\n SkuID: string, // Azure EA\r\n Term: string, // Azure EA + MCA\r\n TierMinimumUnits: real, // Azure MCA\r\n UnitOfMeasure: string, // Azure EA + MCA\r\n UnitPrice: real, // Azure EA + MCA\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Prices_raw ingestion mapping\r\n.create-or-alter table Prices_raw ingestion parquet mapping \"Prices_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"BasePrice\", \"Properties\": { \"Field\": \"BasePrice\" } },\r\n { \"Column\": \"BillingAccountId\", \"Properties\": { \"Field\": \"BillingAccountId\" } },\r\n { \"Column\": \"BillingAccountName\", \"Properties\": { \"Field\": \"BillingAccountName\" } },\r\n { \"Column\": \"BillingCurrency\", \"Properties\": { \"Field\": \"BillingCurrency\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\r\n { \"Column\": \"CurrencyCode\", \"Properties\": { \"Field\": \"CurrencyCode\" } },\r\n { \"Column\": \"EffectiveEndDate\", \"Properties\": { \"Field\": \"EffectiveEndDate\" } },\r\n { \"Column\": \"EffectiveStartDate\", \"Properties\": { \"Field\": \"EffectiveStartDate\" } },\r\n { \"Column\": \"EnrollmentNumber\", \"Properties\": { \"Field\": \"EnrollmentNumber\" } },\r\n { \"Column\": \"IncludedQuantity\", \"Properties\": { \"Field\": \"IncludedQuantity\" } },\r\n { \"Column\": \"MarketPrice\", \"Properties\": { \"Field\": \"MarketPrice\" } },\r\n { \"Column\": \"MeterCategory\", \"Properties\": { \"Field\": \"MeterCategory\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"MeterID\", \"Properties\": { \"Field\": \"MeterID\" } },\r\n { \"Column\": \"MeterName\", \"Properties\": { \"Field\": \"MeterName\" } },\r\n { \"Column\": \"MeterRegion\", \"Properties\": { \"Field\": \"MeterRegion\" } },\r\n { \"Column\": \"MeterSubCategory\", \"Properties\": { \"Field\": \"MeterSubCategory\" } },\r\n { \"Column\": \"MeterType\", \"Properties\": { \"Field\": \"MeterType\" } },\r\n { \"Column\": \"OfferID\", \"Properties\": { \"Field\": \"OfferID\" } },\r\n { \"Column\": \"PartNumber\", \"Properties\": { \"Field\": \"PartNumber\" } },\r\n { \"Column\": \"PriceType\", \"Properties\": { \"Field\": \"PriceType\" } },\r\n { \"Column\": \"Product\", \"Properties\": { \"Field\": \"Product\" } },\r\n { \"Column\": \"ProductId\", \"Properties\": { \"Field\": \"ProductId\" } },\r\n { \"Column\": \"ProductID\", \"Properties\": { \"Field\": \"ProductID\" } },\r\n { \"Column\": \"ServiceFamily\", \"Properties\": { \"Field\": \"ServiceFamily\" } },\r\n { \"Column\": \"SkuId\", \"Properties\": { \"Field\": \"SkuId\" } },\r\n { \"Column\": \"SkuID\", \"Properties\": { \"Field\": \"SkuID\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"TierMinimumUnits\", \"Properties\": { \"Field\": \"TierMinimumUnits\" } },\r\n { \"Column\": \"UnitOfMeasure\", \"Properties\": { \"Field\": \"UnitOfMeasure\" } },\r\n { \"Column\": \"UnitPrice\", \"Properties\": { \"Field\": \"UnitPrice\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Prices_raw retention policy (clear historical data)\r\n.alter-merge table Prices_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Prices_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Prices_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Prices_raw streaming ingestion (required for Fabric)\r\n.alter table Prices_raw policy streamingingestion disable\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_raw table -- Create the table if it doesn't exist\r\n.create-merge table Recommendations_raw ( ignore: string )\r\n\r\n// Recommendations_raw table -- Remove all columns to allow changing column types\r\n.alter table Recommendations_raw ( ignore: string )\r\n\r\n// Recommendations_raw table -- Redefine all columns\r\n.alter table Recommendations_raw (\r\n CostWithNoReservedInstances: real, // MS CM EA resv reco 2024-05-01\r\n CostWithNoReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n FirstUsageDate: datetime, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n InstanceFlexibilityGroup: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n InstanceFlexibilityRatio: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n Location: string, // MS CM EA+MCA resv reco 2024-05-01\r\n LookBackPeriod: string, // MS CM EA+MCA resv reco 2024-05-01\r\n MeterId: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n NetSavings: real, // MS CM EA resv reco 2024-05-01\r\n NetSavingsJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n NormalizedSize: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n ProviderName: string, // Hubs v1_2\r\n RecommendedQuantity: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n RecommendedQuantityNormalized: real, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n ResourceId: string, // Hubs v1_2\r\n ResourceName: string, // Hubs v1_2\r\n ResourceType: string, // Hubs v1_2, MS CM EA+MCA resv reco 2024-05-01\r\n Scope: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n SKU: string, // MS CM EA resv reco 2024-05-01\r\n SkuName: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces\r\n SkuProperties: string, // MS CM EA/MCA resv reco 2024-05-01 -- Renamed for MCA\r\n SubAccountId: string, // Hubs v1_2\r\n SubAccountName: string, // Hubs v1_2\r\n SubscriptionId: string, // MS CM EA+MCA resv reco 2024-05-01\r\n Term: string, // MS CM EA+MCA resv reco 2024-05-01\r\n TotalCostWithReservedInstances: real, // MS CM EA resv reco 2024-05-01\r\n TotalCostWithReservedInstancesJson: string, // MS CM MCA resv reco 2024-05-01 -- Renamed to remove spaces and flag as JSON\r\n x_EffectiveCostAfter: real, // Hubs v1_2\r\n x_EffectiveCostBefore: real, // Hubs v1_2\r\n x_EffectiveCostSavings: real, // Hubs v1_2\r\n x_RecommendationCategory: string, // Hubs v1_2\r\n x_RecommendationDate: datetime, // Hubs v1_2\r\n x_RecommendationDescription: string, // Hubs v1_2\r\n x_RecommendationDetails: dynamic, // Hubs v1_2\r\n x_RecommendationId: string, // Hubs v1_2\r\n x_ResourceGroupName: string, // Hubs v1_2\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Recommendations_raw ingestion mapping\r\n.create-or-alter table Recommendations_raw ingestion parquet mapping \"Recommendations_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"CostWithNoReservedInstances\", \"Properties\": { \"Field\": \"CostWithNoReservedInstances\" } },\r\n { \"Column\": \"CostWithNoReservedInstancesJson\", \"Properties\": { \"Field\": \"CostWithNoReservedInstancesJson\" } },\r\n { \"Column\": \"FirstUsageDate\", \"Properties\": { \"Field\": \"FirstUsageDate\" } },\r\n { \"Column\": \"InstanceFlexibilityGroup\", \"Properties\": { \"Field\": \"InstanceFlexibilityGroup\" } },\r\n { \"Column\": \"InstanceFlexibilityRatio\", \"Properties\": { \"Field\": \"InstanceFlexibilityRatio\" } },\r\n { \"Column\": \"Location\", \"Properties\": { \"Field\": \"Location\" } },\r\n { \"Column\": \"LookBackPeriod\", \"Properties\": { \"Field\": \"LookBackPeriod\" } },\r\n { \"Column\": \"MeterId\", \"Properties\": { \"Field\": \"MeterId\" } },\r\n { \"Column\": \"NetSavings\", \"Properties\": { \"Field\": \"NetSavings\" } },\r\n { \"Column\": \"NetSavingsJson\", \"Properties\": { \"Field\": \"NetSavingsJson\" } },\r\n { \"Column\": \"NormalizedSize\", \"Properties\": { \"Field\": \"NormalizedSize\" } },\r\n { \"Column\": \"ProviderName\", \"Properties\": { \"Field\": \"ProviderName\" } },\r\n { \"Column\": \"RecommendedQuantity\", \"Properties\": { \"Field\": \"RecommendedQuantity\" } },\r\n { \"Column\": \"RecommendedQuantityNormalized\", \"Properties\": { \"Field\": \"RecommendedQuantityNormalized\" } },\r\n { \"Column\": \"ResourceId\", \"Properties\": { \"Field\": \"ResourceId\" } },\r\n { \"Column\": \"ResourceName\", \"Properties\": { \"Field\": \"ResourceName\" } },\r\n { \"Column\": \"ResourceType\", \"Properties\": { \"Field\": \"ResourceType\" } },\r\n { \"Column\": \"Scope\", \"Properties\": { \"Field\": \"Scope\" } },\r\n { \"Column\": \"SKU\", \"Properties\": { \"Field\": \"SKU\" } },\r\n { \"Column\": \"SkuName\", \"Properties\": { \"Field\": \"SkuName\" } },\r\n { \"Column\": \"SkuProperties\", \"Properties\": { \"Field\": \"SkuProperties\" } },\r\n { \"Column\": \"SubAccountId\", \"Properties\": { \"Field\": \"SubAccountId\" } },\r\n { \"Column\": \"SubAccountName\", \"Properties\": { \"Field\": \"SubAccountName\" } },\r\n { \"Column\": \"SubscriptionId\", \"Properties\": { \"Field\": \"SubscriptionId\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"TotalCostWithReservedInstances\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstances\" } },\r\n { \"Column\": \"TotalCostWithReservedInstancesJson\", \"Properties\": { \"Field\": \"TotalCostWithReservedInstancesJson\" } },\r\n { \"Column\": \"x_EffectiveCostAfter\", \"Properties\": { \"Field\": \"x_EffectiveCostAfter\" } },\r\n { \"Column\": \"x_EffectiveCostBefore\", \"Properties\": { \"Field\": \"x_EffectiveCostBefore\" } },\r\n { \"Column\": \"x_EffectiveCostSavings\", \"Properties\": { \"Field\": \"x_EffectiveCostSavings\" } },\r\n { \"Column\": \"x_RecommendationCategory\", \"Properties\": { \"Field\": \"x_RecommendationCategory\" } },\r\n { \"Column\": \"x_RecommendationDate\", \"Properties\": { \"Field\": \"x_RecommendationDate\" } },\r\n { \"Column\": \"x_RecommendationDescription\", \"Properties\": { \"Field\": \"x_RecommendationDescription\" } },\r\n { \"Column\": \"x_RecommendationDetails\", \"Properties\": { \"Field\": \"x_RecommendationDetails\" } },\r\n { \"Column\": \"x_RecommendationId\", \"Properties\": { \"Field\": \"x_RecommendationId\" } },\r\n { \"Column\": \"x_ResourceGroupName\", \"Properties\": { \"Field\": \"x_ResourceGroupName\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Recommendations_raw retention policy (clear historical data)\r\n.alter-merge table Recommendations_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Recommendations_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Recommendations_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Recommendations_raw streaming ingestion (required for Fabric)\r\n.alter table Recommendations_raw policy streamingingestion disable\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_raw table -- Create the table if it doesn't exist\r\n.create-merge table Transactions_raw ( ignore: string )\r\n\r\n// Transactions_raw table -- Remove all columns to allow changing column types\r\n.alter table Transactions_raw ( ignore: string )\r\n\r\n// Transactions_raw table -- Redefine all columns\r\n.alter table Transactions_raw (\r\n AccountName: string, // MS CM EA resv trans 2023-05-01\r\n AccountOwnerEmail: string, // MS CM EA resv trans 2023-05-01\r\n Amount: real, // MS CM EA+MCA resv trans 2023-05-01\r\n ArmSkuName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n BillingFrequency: string, // MS CM EA+MCA resv trans 2023-05-01\r\n BillingMonth: string, // MS CM EA resv trans 2023-05-01\r\n BillingProfileId: string, // MS CM MCA resv trans 2023-05-01\r\n BillingProfileName: string, // MS CM MCA resv trans 2023-05-01\r\n CostCenter: string, // MS CM EA resv trans 2023-05-01\r\n Currency: string, // MS CM EA+MCA resv trans 2023-05-01\r\n CurrentEnrollmentId: string, // MS CM EA resv trans 2023-05-01\r\n DepartmentName: string, // MS CM EA resv trans 2023-05-01\r\n Description: string, // MS CM EA+MCA resv trans 2023-05-01\r\n EventDate: datetime, // MS CM EA+MCA resv trans 2023-05-01\r\n EventType: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Invoice: string, // MS CM EA+MCA resv trans 2023-05-01\r\n InvoiceId: string, // MS CM EA+MCA resv trans 2023-05-01\r\n InvoiceSectionId: string, // MS CM MCA resv trans 2023-05-01\r\n InvoiceSectionName: string, // MS CM MCA resv trans 2023-05-01\r\n MonetaryCommitment: real, // MS CM EA resv trans 2023-05-01\r\n Overage: real, // MS CM EA resv trans 2023-05-01\r\n PurchasingEnrollment: string, // MS CM EA resv trans 2023-05-01\r\n PurchasingSubscriptionGuid: string, // MS CM EA+MCA resv trans 2023-05-01\r\n PurchasingSubscriptionName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Quantity: real, // MS CM EA+MCA resv trans 2023-05-01\r\n Region: string, // MS CM EA+MCA resv trans 2023-05-01\r\n ReservationOrderId: string, // MS CM EA+MCA resv trans 2023-05-01\r\n ReservationOrderName: string, // MS CM EA+MCA resv trans 2023-05-01\r\n Term: string, // MS CM EA+MCA resv trans 2023-05-01\r\n x_SourceName: string, // Hubs v1_0+\r\n x_SourceProvider: string, // Hubs v1_0+\r\n x_SourceType: string, // Hubs v1_0+\r\n x_SourceVersion: string // Hubs v1_0+\r\n)\r\n\r\n// Transactions_raw ingestion mapping\r\n.create-or-alter table Transactions_raw ingestion parquet mapping \"Transactions_raw_mapping\"\r\n```\r\n[\r\n { \"Column\": \"AccountName\", \"Properties\": { \"Field\": \"AccountName\" } },\r\n { \"Column\": \"AccountOwnerEmail\", \"Properties\": { \"Field\": \"AccountOwnerEmail\" } },\r\n { \"Column\": \"Amount\", \"Properties\": { \"Field\": \"Amount\" } },\r\n { \"Column\": \"ArmSkuName\", \"Properties\": { \"Field\": \"ArmSkuName\" } },\r\n { \"Column\": \"BillingFrequency\", \"Properties\": { \"Field\": \"BillingFrequency\" } },\r\n { \"Column\": \"BillingMonth\", \"Properties\": { \"Field\": \"BillingMonth\" } },\r\n { \"Column\": \"BillingProfileId\", \"Properties\": { \"Field\": \"BillingProfileId\" } },\r\n { \"Column\": \"BillingProfileName\", \"Properties\": { \"Field\": \"BillingProfileName\" } },\r\n { \"Column\": \"CostCenter\", \"Properties\": { \"Field\": \"CostCenter\" } },\r\n { \"Column\": \"Currency\", \"Properties\": { \"Field\": \"Currency\" } },\r\n { \"Column\": \"CurrentEnrollmentId\", \"Properties\": { \"Field\": \"CurrentEnrollmentId\" } },\r\n { \"Column\": \"DepartmentName\", \"Properties\": { \"Field\": \"DepartmentName\" } },\r\n { \"Column\": \"Description\", \"Properties\": { \"Field\": \"Description\" } },\r\n { \"Column\": \"EventDate\", \"Properties\": { \"Field\": \"EventDate\" } },\r\n { \"Column\": \"EventType\", \"Properties\": { \"Field\": \"EventType\" } },\r\n { \"Column\": \"Invoice\", \"Properties\": { \"Field\": \"Invoice\" } },\r\n { \"Column\": \"InvoiceId\", \"Properties\": { \"Field\": \"InvoiceId\" } },\r\n { \"Column\": \"InvoiceSectionId\", \"Properties\": { \"Field\": \"InvoiceSectionId\" } },\r\n { \"Column\": \"InvoiceSectionName\", \"Properties\": { \"Field\": \"InvoiceSectionName\" } },\r\n { \"Column\": \"MonetaryCommitment\", \"Properties\": { \"Field\": \"MonetaryCommitment\" } },\r\n { \"Column\": \"Overage\", \"Properties\": { \"Field\": \"Overage\" } },\r\n { \"Column\": \"PurchasingEnrollment\", \"Properties\": { \"Field\": \"PurchasingEnrollment\" } },\r\n { \"Column\": \"PurchasingSubscriptionGuid\", \"Properties\": { \"Field\": \"PurchasingSubscriptionGuid\" } },\r\n { \"Column\": \"PurchasingSubscriptionName\", \"Properties\": { \"Field\": \"PurchasingSubscriptionName\" } },\r\n { \"Column\": \"Quantity\", \"Properties\": { \"Field\": \"Quantity\" } },\r\n { \"Column\": \"Region\", \"Properties\": { \"Field\": \"Region\" } },\r\n { \"Column\": \"ReservationOrderId\", \"Properties\": { \"Field\": \"ReservationOrderId\" } },\r\n { \"Column\": \"ReservationOrderName\", \"Properties\": { \"Field\": \"ReservationOrderName\" } },\r\n { \"Column\": \"Term\", \"Properties\": { \"Field\": \"Term\" } },\r\n { \"Column\": \"x_SourceName\", \"Properties\": { \"Field\": \"x_SourceName\" } },\r\n { \"Column\": \"x_SourceProvider\", \"Properties\": { \"Field\": \"x_SourceProvider\" } },\r\n { \"Column\": \"x_SourceType\", \"Properties\": { \"Field\": \"x_SourceType\" } },\r\n { \"Column\": \"x_SourceVersion\", \"Properties\": { \"Field\": \"x_SourceVersion\" } }\r\n]\r\n```\r\n\r\n// Transactions_raw retention policy (clear historical data)\r\n.alter-merge table Transactions_raw policy retention softdelete = 0d recoverability = disabled\r\n\r\n// Transactions_raw retention policy (set the user-defined retention period)\r\n.alter-merge table Transactions_raw policy retention softdelete = $$rawRetentionInDays$$d recoverability = disabled\r\n\r\n// Disable Transactions_raw streaming ingestion (required for Fabric)\r\n.alter table Transactions_raw policy streamingingestion disable\r\n\r\n", + "$fxv#9": "// Copyright (c) Microsoft Corporation.\r\n// Licensed under the MIT License.\r\n\r\n//======================================================================================================================\r\n// Ingestion database\r\n// Used for data ingestion, normalization, and cleansing.\r\n// See known issues @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n//======================================================================================================================\r\n\r\n// For allowed commands, see https://learn.microsoft.com/azure/data-explorer/database-script\r\n\r\n//===| Prices |=========================================================================================================\r\n// Supported versions:\r\n// - MS EA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-ea\r\n// - MS MCA 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/price-sheet-mca\r\n//======================================================================================================================\r\n\r\n// Prices_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All prices transformed to FOCUS 1.0. Use Prices_transform_v1_2() instead.', folder='Prices')\r\nPrices_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n let prices = materialize(\r\n Prices_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n BasePrice = todecimal(BasePrice),\r\n IncludedQuantity = todecimal(IncludedQuantity),\r\n MarketPrice = todecimal(MarketPrice),\r\n TierMinimumUnits = todecimal(TierMinimumUnits),\r\n UnitPrice = todecimal(UnitPrice)\r\n //\r\n | extend x_SkuId = coalesce(SkuId, SkuID)\r\n | extend x_SkuMeterId = coalesce(MeterId, MeterID)\r\n | extend x_SkuProductId = coalesce(ProductId, ProductID)\r\n | extend x_SkuTerm = isoMonths(Term)\r\n | project-rename\r\n x_BaseUnitPrice = BasePrice,\r\n x_EffectivePeriodEnd = EffectiveEndDate,\r\n x_EffectivePeriodStart = EffectiveStartDate,\r\n x_PricingUnitDescription = UnitOfMeasure,\r\n x_SkuIncludedQuantity = IncludedQuantity,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuMeterType = MeterType,\r\n x_SkuOfferId = OfferID,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPriceType = PriceType,\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTier = TierMinimumUnits\r\n | extend ContractedUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', UnitPrice, todecimal('')) // UnitPrice for savings plan is not the on-demand unit price\r\n | extend ListUnitPrice = iff(x_SkuPriceType != 'SavingsPlan', MarketPrice, todecimal('')) // MarketPrice for savings plan is not the list price\r\n | extend ChargeCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Usage',\r\n x_SkuPriceType == 'ReservedInstance', 'Purchase',\r\n x_SkuPriceType == 'SavingsPlan', 'Usage', // Savings plan prices are for committed usage, not the purchase\r\n ''\r\n )\r\n | extend SkuPriceIdv2 = strcat(case(x_SkuPriceType == 'Consumption', 'OD', x_SkuPriceType == 'ReservedInstance', 'RI', x_SkuPriceType == 'SavingsPlan', 'SP', 'XX'), substring(ChargeCategory, 0, 1), x_SkuTerm, '_', x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType, '_', x_SkuTier, x_SkuOfferId)\r\n | extend x_BillingAccountId = iff(BillingAccountId startswith '/', split(BillingAccountId, '/')[-1], coalesce(BillingAccountId, EnrollmentNumber))\r\n | extend x_BillingProfileId = iff(BillingProfileId startswith '/', split(BillingProfileId, '/')[-1], coalesce(BillingProfileId, EnrollmentNumber))\r\n | extend tmp_SavingsPlanKey = strcat(x_SkuMeterId, x_SkuProductId, x_SkuId, x_SkuTier, x_SkuOfferId)\r\n //\r\n // Get latest ingested row based on the unique ID\r\n | extend x_IngestionTime = ingestion_time()\r\n );\r\n //\r\n // Meters for reservations and savings plans to identify commitment eligibility\r\n let riMeters = prices | where x_SkuPriceType == 'ReservedInstance' | distinct x_SkuMeterId;\r\n let spMeters = prices | where x_SkuPriceType == 'SavingsPlan' | distinct x_SkuMeterId;\r\n //\r\n // Copy list/base/contracted prices from on-demand SKUs\r\n prices\r\n | where x_SkuPriceType == 'SavingsPlan'\r\n // If we use join, specify the shuffle key\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter hint.strategy=shuffle (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | lookup kind=leftouter (prices | where x_SkuPriceType == 'Consumption' | where x_SkuMeterId in (spMeters) | distinct tmp_SavingsPlanKey, ListUnitPrice, ContractedUnitPrice, x_BaseUnitPrice) on tmp_SavingsPlanKey\r\n | extend ListUnitPrice = coalesce(ListUnitPrice, ListUnitPrice1)\r\n | extend ContractedUnitPrice = coalesce(ContractedUnitPrice, ContractedUnitPrice1)\r\n | extend x_BaseUnitPrice = coalesce(x_BaseUnitPrice, x_BaseUnitPrice1)\r\n | project-away ListUnitPrice1, ContractedUnitPrice1, x_BaseUnitPrice1, tmp_SavingsPlanKey\r\n | union ((prices | where x_SkuPriceType != 'SavingsPlan'))\r\n //\r\n // Calculate commitment discount elgibility\r\n // TODO: Would a join be faster?\r\n | extend x_CommitmentDiscountSpendEligibility = iff(x_SkuMeterId in (riMeters) and x_SkuPriceType != 'ReservedInstance', 'Eligible', 'Not Eligible')\r\n | extend x_CommitmentDiscountUsageEligibility = iff(x_SkuMeterId in (spMeters), 'Eligible', 'Not Eligible')\r\n //\r\n // Add PricingUnit and x_PricingBlockSize\r\n // TODO: Compare join vs. lookup perf -- | join kind=leftouter (PricingUnits) on x_PricingUnitDescription | project-away x_PricingUnitDescription1\r\n | lookup kind=leftouter (PricingUnits | extend x_PricingBlockSize = todecimal(x_PricingBlockSize)) on x_PricingUnitDescription\r\n //\r\n | extend x_EffectiveUnitPrice = iff(x_SkuPriceType == 'SavingsPlan', UnitPrice, todecimal('')) // Savings plan prices are for the effective price, not the contracted price\r\n | extend x_EffectiveUnitPriceDiscount = ContractedUnitPrice - x_EffectiveUnitPrice\r\n | extend x_ContractedUnitPriceDiscount = ListUnitPrice - ContractedUnitPrice\r\n | extend x_TotalUnitPriceDiscount = ListUnitPrice - x_EffectiveUnitPrice\r\n | project\r\n BillingAccountId = tolower(case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n BillingAccountId startswith '/', BillingAccountId,\r\n strcat('/providers/microsoft.billing/billingaccounts/', x_BillingAccountId, iff(x_BillingProfileId == x_BillingAccountId, '', strcat('/billingprofiles/', x_BillingProfileId)))\r\n )),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName, x_BillingProfileId),\r\n BillingCurrency = coalesce(BillingCurrency, CurrencyCode, Currency), // Currency last as a fallback only\r\n ChargeCategory,\r\n CommitmentDiscountCategory = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Usage',\r\n x_SkuPriceType == 'SavingsPlan', 'Spend',\r\n ''\r\n ),\r\n CommitmentDiscountType = case(\r\n x_SkuPriceType == 'ReservedInstance', 'Reservation',\r\n x_SkuPriceType == 'SavingsPlan', 'Savings plan',\r\n ''\r\n ),\r\n ContractedUnitPrice,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed',\r\n ''\r\n ),\r\n PricingUnit,\r\n SkuId = coalesce(ProductId, ProductID),\r\n SkuPriceId = strcat(x_SkuProductId, '_', x_SkuId, '_', x_SkuMeterType),\r\n SkuPriceIdv2,\r\n x_BaseUnitPrice,\r\n x_BillingAccountAgreement = case(\r\n strlen(x_BillingAccountId) > 32, 'MCA',\r\n strlen(x_BillingAccountId) < 32, 'EA',\r\n 'Unknown'\r\n ),\r\n x_BillingAccountId,\r\n x_BillingProfileId,\r\n x_CommitmentDiscountSpendEligibility,\r\n x_CommitmentDiscountUsageEligibility,\r\n x_ContractedUnitPriceDiscount,\r\n x_ContractedUnitPriceDiscountPercent = 1.0 * x_ContractedUnitPriceDiscount / ListUnitPrice * 100,\r\n x_EffectivePeriodEnd = startofmonth(x_EffectivePeriodEnd + 1h),\r\n x_EffectivePeriodStart,\r\n x_EffectiveUnitPrice,\r\n x_EffectiveUnitPriceDiscount,\r\n x_EffectiveUnitPriceDiscountPercent = 1.0 * x_EffectiveUnitPriceDiscount / ContractedUnitPrice * 100,\r\n x_IngestionTime,\r\n x_PricingBlockSize,\r\n x_PricingCurrency = coalesce(Currency, CurrencyCode), // CurrencyCode last as a fallback only\r\n x_PricingSubcategory = case(\r\n x_SkuPriceType == 'Consumption' and (x_SkuIncludedQuantity > 0 or x_SkuTier > 0), 'Tiered',\r\n x_SkuPriceType == 'Consumption', 'Standard',\r\n x_SkuPriceType == 'ReservedInstance', 'Standard', // Reservation purchases are tracked as \"Standard\"\r\n x_SkuPriceType == 'SavingsPlan', 'Committed Spend',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_SkuDescription = Product,\r\n x_SkuId,\r\n x_SkuIncludedQuantity,\r\n x_SkuMeterCategory,\r\n x_SkuMeterId,\r\n x_SkuMeterName,\r\n x_SkuMeterSubcategory,\r\n x_SkuMeterType,\r\n x_SkuPriceType,\r\n x_SkuProductId,\r\n x_SkuRegion,\r\n x_SkuServiceFamily,\r\n x_SkuOfferId,\r\n x_SkuPartNumber,\r\n x_SkuTerm,\r\n x_SkuTier,\r\n x_SourceName = coalesce(x_SourceName, 'Cost Management'),\r\n x_SourceProvider = coalesce(x_SourceProvider, 'Microsoft'),\r\n x_SourceType = coalesce(x_SourceType, 'PriceSheet'),\r\n x_SourceVersion = coalesce(x_SourceVersion, '2023-05-01'),\r\n x_TotalUnitPriceDiscount,\r\n x_TotalUnitPriceDiscountPercent = 1.0 * x_TotalUnitPriceDiscount / ListUnitPrice * 100\r\n}\r\n\r\n// Prices_final_v1_0 table\r\n.create-merge table Prices_final_v1_0 (\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingCurrency: string,\r\n ChargeCategory: string,\r\n CommitmentDiscountCategory: string,\r\n CommitmentDiscountType: string,\r\n ContractedUnitPrice: decimal,\r\n ListUnitPrice: decimal,\r\n PricingCategory: string,\r\n PricingUnit: string,\r\n SkuId: string,\r\n SkuPriceId: string,\r\n SkuPriceIdv2: string, // Hubs add-on\r\n x_BaseUnitPrice: decimal, // Azure\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure MCA\r\n x_BillingProfileId: string, // Azure MCA\r\n x_CommitmentDiscountSpendEligibility: string, // Hubs add-on\r\n x_CommitmentDiscountUsageEligibility: string, // Hubs add-on\r\n x_ContractedUnitPriceDiscount: decimal, // Hubs add-on\r\n x_ContractedUnitPriceDiscountPercent: decimal, // Hubs add-on\r\n x_EffectivePeriodEnd: datetime, // Azure\r\n x_EffectivePeriodStart: datetime, // Azure\r\n x_EffectiveUnitPrice: decimal, // Azure\r\n x_EffectiveUnitPriceDiscount: decimal, // Hubs add-on\r\n x_EffectiveUnitPriceDiscountPercent: decimal, // Hubs add-on\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_PricingBlockSize: decimal, // Hubs add-on\r\n x_PricingCurrency: string, // Azure\r\n x_PricingSubcategory: string, // Hubs add-on\r\n x_PricingUnitDescription: string, // Azure\r\n x_SkuDescription: string, // Azure\r\n x_SkuId: string, // Azure\r\n x_SkuIncludedQuantity: decimal, // Azure EA\r\n x_SkuMeterCategory: string, // Azure\r\n x_SkuMeterId: string, // Azure\r\n x_SkuMeterName: string, // Azure\r\n x_SkuMeterSubcategory: string, // Azure\r\n x_SkuMeterType: string, // Azure\r\n x_SkuPriceType: string, // Azure\r\n x_SkuProductId: string, // Azure\r\n x_SkuRegion: string, // Azure\r\n x_SkuServiceFamily: string, // Azure\r\n x_SkuOfferId: string, // Azure EA\r\n x_SkuPartNumber: string, // Azure EA\r\n x_SkuTerm: int, // Azure\r\n x_SkuTier: decimal, // Azure MCA\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_TotalUnitPriceDiscount: decimal, // Hubs add-on\r\n x_TotalUnitPriceDiscountPercent: decimal // Hubs add-on\r\n)\r\n\r\n// Update policy for Prices_raw -> Prices_final_v1_0\r\n.alter table Prices_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Prices_raw\",\r\n \"Query\": \"Prices_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Cost and usage |=================================================================================================\r\n// Supported versions:\r\n// - MS: 1.0, 1.0-preview(v1) -- See https://aka.ms/costmgmt/exports/focus\r\n// - AWS: 1.0 -- See https://docs.aws.amazon.com/cur/latest/userguide/table-dictionary-focus-1-0-aws-columns.html\r\n// - GCP: Jan-Jun 2024 -- See https://cloud.google.com/resources/google-cloud-focus?e=48754805&hl=en\r\n// Links to (Aug 2024): https://services.google.com/fh/files/misc/focus_guide_v1.pdf\r\n// See also:\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/standard-usage\r\n// - https://cloud.google.com/billing/docs/how-to/export-data-bigquery-tables/detailed-usage\r\n// - OCI: 1.0 -- See https://docs.oracle.com/iaas/Content/Billing/Concepts/costusagereportsoverview.htm#costreports__focus-cost-report-schema\r\n//\r\n// Support for non-Azure data is limited to ingestion only. Data is not transformed across versions.\r\n//======================================================================================================================\r\n\r\n// Costs_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All costs transformed to FOCUS 1.0. Use Costs_transform_v1_2() instead.', folder='Costs')\r\nCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n Costs_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n BilledCost = todecimal(BilledCost),\r\n CommitmentDiscountQuantity = todecimal(CommitmentDiscountQuantity),\r\n ConsumedQuantity = todecimal(ConsumedQuantity),\r\n ContractedCost = todecimal(ContractedCost),\r\n ContractedUnitPrice = todecimal(ContractedUnitPrice),\r\n EffectiveCost = todecimal(EffectiveCost),\r\n ListCost = todecimal(ListCost),\r\n ListUnitPrice = todecimal(ListUnitPrice),\r\n PricingQuantity = todecimal(PricingQuantity),\r\n UsageAmount = todecimal(UsageAmount),\r\n UsageQuantity = todecimal(UsageQuantity),\r\n x_BilledCostInUsd = todecimal(x_BilledCostInUsd),\r\n x_BilledUnitPrice = todecimal(x_BilledUnitPrice),\r\n x_BillingExchangeRate = todecimal(x_BillingExchangeRate),\r\n x_ContractedCostInUsd = todecimal(x_ContractedCostInUsd),\r\n x_Cost = todecimal(x_Cost),\r\n x_CurrencyConversionRate = todecimal(x_CurrencyConversionRate),\r\n x_EffectiveCostInUsd = todecimal(x_EffectiveCostInUsd),\r\n x_EffectiveUnitPrice = todecimal(x_EffectiveUnitPrice),\r\n x_ListCostInUsd = todecimal(x_ListCostInUsd),\r\n x_OnDemandCost = todecimal(x_OnDemandCost),\r\n x_OnDemandCostInUsd = todecimal(x_OnDemandCostInUsd),\r\n x_OnDemandUnitPrice = todecimal(x_OnDemandUnitPrice),\r\n x_PricingBlockSize = todecimal(x_PricingBlockSize)\r\n //\r\n // Dedupe rows\r\n | extend x_IngestionTime = ingestion_time()\r\n | extend x_ChargeId = ''\r\n // TODO: Consider adding a unique charge ID per row\r\n // hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // // 1. Resource hierarchy (including resource name), highest to lowest\r\n // BillingAccountId,\r\n // x_InvoiceSectionId,\r\n // x_AccountOwnerId,\r\n // SubAccountId,\r\n // x_ResourceGroupName,\r\n // ResourceName,\r\n // // 2. Resource details\r\n // ResourceId,\r\n // RegionId,\r\n // Tags,\r\n // CommitmentDiscountId,\r\n // x_CostCenter,\r\n // // 4. Meter details\r\n // SkuPriceId,\r\n // x_SkuMeterId,\r\n // x_SkuPartNumber,\r\n // x_SkuOfferId,\r\n // x_SkuDetails,\r\n // // 5. Date\r\n // ChargePeriodStart\r\n // ))\r\n //\r\n // Identify data quality issues\r\n | extend x_SourceChanges = trim_end(',', strcat(\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based', 'InvalidChargeFrequency,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and EffectiveCost > 0, 'InvalidEffectiveCost,', ''),\r\n iff((isempty(ContractedCost) or ContractedCost == 0) and EffectiveCost != 0, 'MissingContractedCost,', ''),\r\n iff((isempty(ContractedUnitPrice) or ContractedUnitPrice == 0) and x_EffectiveUnitPrice != 0, 'MissingContractedUnitPrice,', ''),\r\n iff(ListCost < ContractedCost, 'ListCostLessThanContractedCost,', ''),\r\n iff(ContractedCost < EffectiveCost, 'ContractedCostLessThanEffectiveCost,', ''),\r\n iff((isempty(ListCost) or ListCost == 0) and (ContractedCost != 0 or EffectiveCost != 0), 'MissingListCost,', ''),\r\n iff((isempty(ListUnitPrice) or ListUnitPrice == 0) and (ContractedUnitPrice != 0 or x_EffectiveUnitPrice != 0), 'MissingListUnitPrice,', ''),\r\n iff((isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001)\r\n or (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001),\r\n 'XEffectiveUnitPriceRoundingError,', ''),\r\n iff(ConsumedQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingConsumedQuantity,', ''),\r\n iff(PricingQuantity == 0 and (EffectiveCost != 0 or BilledCost != 0), 'MissingPricingQuantity,', ''),\r\n iff(isempty(ProviderName), 'MissingProviderName,', ''),\r\n iff(isempty(PublisherName), 'MissingPublisherName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceId), 'MissingResourceId,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceName), 'MissingResourceName,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(ResourceType), 'MissingResourceType,', ''),\r\n iff(BilledCost > 0 and x_BilledUnitPrice == 0, 'MissingXBilledUnitPrice,', ''),\r\n iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and isempty(x_ResourceType), 'MissingXResourceType,', ''),\r\n iff(PricingCategory == 'Standard' and isnotempty(CommitmentDiscountId) and ChargeCategory == 'Usage', 'PricingCategoryShouldBeCommitted,', ''),\r\n iff(x_SkuTerm == '1Year' or x_SkuTerm == '3Years' or x_SkuTerm == '5Years', 'SkuTermShouldBeAnInteger,', '')\r\n ))\r\n //\r\n // Fix columns needed in other changes\r\n | extend ProviderName = case(\r\n isnotempty(ProviderName), ProviderName,\r\n isnotempty(coalesce(x_CostCategories, x_Discount, x_Operation, x_ServiceCode, x_UsageType)), 'AWS',\r\n isnotempty(coalesce(tostring(UsageAmount), tostring(x_Cost), x_Credits, x_CostType, tostring(x_CurrencyConversionRate), tostring(x_ExportTime), x_Project, x_ServiceId)), 'GCP',\r\n isnotempty(coalesce(x_BillingProfileId, x_InvoiceSectionId)), 'Microsoft',\r\n ''\r\n )\r\n //\r\n // Identify source\r\n | extend x_SourceName = coalesce(x_SourceName, iff(isnotempty(x_BillingProfileId), 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(isnotempty(x_BillingProfileId), 'FocusCost', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, case(\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)) and isempty(SkuPriceDetails) and isnotempty(x_SkuDetails), '1.2-preview',\r\n isnotempty(coalesce(InvoiceId, SkuMeter, PricingCurrency)), '1.2',\r\n isnotempty(coalesce(ChargeClass, CommitmentDiscountStatus, tostring(ConsumedQuantity), ConsumedUnit, tostring(ContractedCost), tostring(ContractedUnitPrice), RegionId, RegionName)), '1.0',\r\n isnotempty(coalesce(ChargeSubcategory, Region, tostring(UsageQuantity), UsageUnit)), iff(ProviderName == 'Microsoft', '1.0-preview(v1)', '1.0-preview'),\r\n ''\r\n ))\r\n // Append version check error code\r\n | extend x_SourceChanges = iff(x_SourceVersion == '1.0', x_SourceChanges,\r\n strcat(x_SourceChanges, iff(isempty(x_SourceChanges), '', ','), iff(x_SourceVersion == '', 'UnknownFocusVersion', 'LegacyFocusVersion'))\r\n )\r\n //\r\n // Fix quantities\r\n | extend PricingQuantity = case(\r\n PricingQuantity != 0 or (EffectiveCost == 0 and BilledCost == 0), PricingQuantity,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(BilledCost) and BilledCost != 0 and isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, BilledCost / x_BilledUnitPrice,\r\n PricingQuantity == 0 and isnotempty(EffectiveCost) and EffectiveCost != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0, EffectiveCost / x_EffectiveUnitPrice,\r\n PricingQuantity\r\n )\r\n | extend ConsumedQuantity = case(\r\n isnotempty(ConsumedQuantity) and ConsumedQuantity != 0, ConsumedQuantity,\r\n ChargeCategory == 'Usage', PricingQuantity / coalesce(x_PricingBlockSize, decimal(1)),\r\n ConsumedQuantity\r\n )\r\n //\r\n // Populate missing prices -- mapping to on-demand prices requires meter ID and offer ID\r\n | extend tmp_MissingPrices = ProviderName == 'Microsoft'\r\n and (ListUnitPrice == 0 or ContractedUnitPrice == 0)\r\n and x_EffectiveUnitPrice != 0\r\n and not(CommitmentDiscountCategory == 'Spend' and CommitmentDiscountStatus == 'Unused')\r\n and isnotempty(strcat(x_SkuMeterId, x_SkuOfferId))\r\n | as allCosts\r\n | where tmp_MissingPrices\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(ChargePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | as costsWithMissingPrices\r\n | join kind=leftouter (\r\n Prices_final_v1_0\r\n | extend tmp_ReservationPriceLookupKey = tolower(strcat(x_BillingProfileId, substring(x_EffectivePeriodStart, 0, 7), x_SkuMeterId, x_SkuOfferId))\r\n | where x_SkuPriceType == 'Consumption' and tmp_ReservationPriceLookupKey in ((costsWithMissingPrices | summarize by tmp_ReservationPriceLookupKey))\r\n | summarize ListUnitPrice = min(ListUnitPrice), ContractedUnitPrice = min(ContractedUnitPrice) by tmp_ReservationPriceLookupKey, x_PricingBlockSize, PricingUnit\r\n ) on tmp_ReservationPriceLookupKey\r\n //\r\n // Select the best price to use for each row\r\n // TODO: Save values before changing -- | extend x_old_ContractedUnitPrice = ContractedUnitPrice, x_old_EffectiveUnitPrice = x_EffectiveUnitPrice, x_old_ListUnitPrice = ListUnitPrice, x_old_ListCost = ListCost, x_old_ContractedCost = ContractedCost\r\n | extend x_EffectiveUnitPrice = case(\r\n // If price is a rounding error away from the billed price, use the billed price\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(x_BilledUnitPrice - x_EffectiveUnitPrice) < 0.0001, x_BilledUnitPrice,\r\n // If price is a rounding error away from the contracted price, use the contracted price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0 and isnotempty(x_EffectiveUnitPrice) and x_EffectiveUnitPrice != 0 and abs(ContractedUnitPrice - x_EffectiveUnitPrice) < 0.0001, ContractedUnitPrice,\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ContractedUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ContractedUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ContractedUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // If billed price is available, assume the billed price is the same as contracted price to support aggregations\r\n isnotempty(x_BilledUnitPrice) and x_BilledUnitPrice != 0, x_EffectiveUnitPrice,\r\n // Otherwise, assume the effective price is the same as contracted price to support aggregations\r\n x_EffectiveUnitPrice\r\n )\r\n | extend ListUnitPrice = case(\r\n // If price is already correct, keep that\r\n (isnotempty(ListUnitPrice) and ListUnitPrice != 0) or (EffectiveCost == 0 and BilledCost == 0), ListUnitPrice,\r\n // If both prices use the same scale, use the new one\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize == x_PricingBlockSize1, ListUnitPrice1 * x_BillingExchangeRate,\r\n // If prices are the same unit but not the same scale, use the new one but correct the scale\r\n PricingUnit == PricingUnit1 and x_PricingBlockSize != x_PricingBlockSize1 and isnotempty(x_PricingBlockSize) and isnotempty(x_PricingBlockSize1), ListUnitPrice1 * x_BillingExchangeRate / x_PricingBlockSize1 * x_PricingBlockSize,\r\n // Otherwise, assume the contracted price is the same as list price to support aggregations\r\n ContractedUnitPrice\r\n )\r\n // Calculate missing costs based on new prices -- If cost is already correct, keep that; if not and price is available, recalculate the cost; otherwise, keep the existing cost\r\n | extend ContractedCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ContractedCost) and ContractedCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ContractedCost,\r\n // ContractedCost is 0 in all other scenarios...\r\n // If 0 and there's a billed cost and prices are the same, use BilledCost\r\n isnotempty(BilledCost) and BilledCost != 0 and ContractedUnitPrice == x_BilledUnitPrice, BilledCost,\r\n // If 0 and there's a billed cost and prices are the same, use EffectiveCost\r\n isnotempty(EffectiveCost) and EffectiveCost != 0 and ContractedUnitPrice == x_EffectiveUnitPrice, EffectiveCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ContractedUnitPrice) and ContractedUnitPrice != 0, ContractedUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume EffectiveCost\r\n isempty(ContractedUnitPrice) or ContractedUnitPrice == 0, EffectiveCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ContractedCost\r\n )\r\n | extend ListCost = case(\r\n // If not set or there's no cost, keep the original value\r\n (isnotempty(ListCost) and ListCost != 0) or (EffectiveCost == 0 and BilledCost == 0), ListCost,\r\n // ListCost is 0 in all other scenarios...\r\n // If 0 and there's a contracted cost and prices are the same, use ContractedCost\r\n isnotempty(ContractedCost) and ContractedCost != 0 and ListUnitPrice == ContractedUnitPrice, ContractedCost,\r\n // If 0 and there's a price, calculate the cost based on the price\r\n isnotempty(ListUnitPrice) and ListUnitPrice != 0, ListUnitPrice * PricingQuantity,\r\n // If 0 and there's no price, assume ContractedCost\r\n isempty(ListUnitPrice) or ListUnitPrice == 0, ContractedCost,\r\n // Fall back to the original value for any unhandled scenarios\r\n ListCost\r\n )\r\n // Merge the rest of the unmodified cost records and remove excess columns\r\n | union (allCosts | where not(tmp_MissingPrices))\r\n | project-away x_PricingBlockSize1, PricingUnit1, ListUnitPrice1, ContractedUnitPrice1, tmp_MissingPrices, tmp_ReservationPriceLookupKey, tmp_ReservationPriceLookupKey1\r\n //\r\n // BUG: Fix ContractedCost that has bad values\r\n | extend ContractedCost = iff(ProviderName == 'Microsoft' and isnotempty(PricingQuantity) and isnotempty(x_PricingBlockSize) and ContractedCost != ContractedUnitPrice * PricingQuantity, ContractedUnitPrice * PricingQuantity, ContractedCost)\r\n //\r\n // Handle FOCUS 1.0-preview UsageQuantity/Unit\r\n | extend ConsumedQuantity = iff(ChargeCategory == 'Usage', coalesce(ConsumedQuantity, UsageQuantity, UsageAmount), todecimal(''))\r\n | extend ConsumedUnit = iff(ChargeCategory == 'Usage' and isnotempty(ConsumedQuantity), coalesce(ConsumedUnit, UsageUnit, 'Units'), '')\r\n //\r\n // Convert IDs to lowercase for consistency\r\n | extend CommitmentDiscountId = tolower(CommitmentDiscountId)\r\n //\r\n // BUG: Remove EffectiveCost for commitment discount purchases\r\n | extend EffectiveCost = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), EffectiveCost)\r\n | extend x_EffectiveCostInUsd = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), decimal(0), x_EffectiveCostInUsd)\r\n //\r\n // Clean up resource columns\r\n | extend ResourceId = case(\r\n isnotempty(ResourceId), ResourceId,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId), CommitmentDiscountId,\r\n ResourceId)\r\n | extend ResourceName = tolower(case(\r\n isnotempty(ResourceName), ResourceName,\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountName), CommitmentDiscountName,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).ResourceName,\r\n ResourceName))\r\n | extend x_ResourceType = case(\r\n isnotempty(x_ResourceType), x_ResourceType,\r\n isnotempty(ResourceId), parse_resourceid(ResourceId).x_ResourceType,\r\n x_ResourceType)\r\n | extend ResourceType = case(\r\n // Use existing resource type display name unless it's an internal resource type ID\r\n isnotempty(ResourceType) and tolower(ResourceType) != tolower(x_ResourceType) and ResourceType !contains '/', ResourceType,\r\n // Use CommitmentDiscountType for commitment discount purchases\r\n ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountType), CommitmentDiscountType,\r\n // Look up display name from internal type\r\n isnotempty(x_ResourceType), coalesce(resource_type(x_ResourceType).SingularDisplayName, ResourceType, x_ResourceType),\r\n ResourceType)\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n AvailabilityZone,\r\n BilledCost,\r\n BillingAccountId = tolower(BillingAccountId),\r\n BillingAccountName,\r\n BillingAccountType,\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEnd),\r\n BillingPeriodStart = startofmonth(BillingPeriodStart),\r\n ChargeCategory = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Credit', 'Credit',\r\n ChargeSubcategory == 'Refund', 'Purchase', // We are assuming purchase refunds since we don't have data to indicate usage refunds\r\n ChargeCategory\r\n ),\r\n ChargeClass = case(ChargeSubcategory == 'Refund', 'Correction', ChargeClass),\r\n ChargeDescription,\r\n // BUG: ChargeFrequency shows \"Usage-Based\" for monthly recurring savings plan purchases\r\n ChargeFrequency = iff(ChargeCategory == 'Purchase' and isnotempty(CommitmentDiscountId) and ChargeFrequency == 'Usage-Based' and ProviderName == 'Microsoft' and x_SourceVersion startswith '1.0', 'Recurring', ChargeFrequency),\r\n ChargePeriodEnd,\r\n ChargePeriodStart,\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = tolower(CommitmentDiscountId),\r\n CommitmentDiscountName,\r\n CommitmentDiscountStatus = case(\r\n // Handle FOCUS 1.0-preview ChargeSubcategory\r\n ChargeSubcategory == 'Used Commitment', 'Used',\r\n ChargeSubcategory == 'Unused Commitment', 'Unused',\r\n CommitmentDiscountStatus\r\n ),\r\n CommitmentDiscountType,\r\n ConsumedQuantity,\r\n ConsumedUnit,\r\n ContractedCost = coalesce(ContractedCost, x_OnDemandCost, x_Cost),\r\n ContractedUnitPrice = coalesce(ContractedUnitPrice, x_OnDemandUnitPrice),\r\n EffectiveCost,\r\n InvoiceIssuerName,\r\n ListCost,\r\n ListUnitPrice,\r\n PricingCategory = case(\r\n // Handle FOCUS 1.0-preview PricingCategory values\r\n PricingCategory == 'On-Demand', 'Standard',\r\n PricingCategory == 'Commitment-Based', 'Committed',\r\n PricingCategory\r\n ),\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName,\r\n // Handle missing PublisherName values\r\n PublisherName = case(PublisherName == 'Microsoft Corporation', 'Microsoft', isnotempty(PublisherName), PublisherName, x_PublisherCategory == 'Cloud Provider', ProviderName, ''),\r\n // Handle FOCUS 1.0-preview Region column\r\n RegionId = coalesce(RegionId, iff(ProviderName == 'Microsoft', replace_string(tolower(Region), ' ', ''), Region)),\r\n RegionName = coalesce(RegionName, Region),\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SkuId,\r\n SkuPriceId,\r\n SubAccountId,\r\n SubAccountName,\r\n SubAccountType, // Azure 1.0-preview(v1)+\r\n Tags = parse_json(Tags),\r\n x_AccountId, // Azure 1.0-preview(v1)+\r\n x_AccountName, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId, // Azure 1.0-preview(v1)+\r\n x_BilledCostInUsd, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement = case(\r\n ProviderName == 'Microsoft' and x_BillingAccountId == x_BillingProfileId, 'EA',\r\n ProviderName == 'Microsoft' and x_BillingAccountId != x_BillingProfileId, 'MCA',\r\n ProviderName\r\n ), // Hubs add-on\r\n x_BillingAccountId, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate, // Azure 1.0-preview(v1)+\r\n x_BillingProfileId, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName, // Azure 1.0-preview(v1)+\r\n x_ChargeId, // Azure 1.0-preview(v1) only\r\n x_ContractedCostInUsd = coalesce(x_ContractedCostInUsd, x_OnDemandCostInUsd), // Azure 1.0+\r\n x_CostAllocationRuleName, // Azure 1.0-preview(v1)+\r\n x_CostCategories = parse_json(x_CostCategories), // AWS 1.0 (JSON)\r\n x_CostCenter, // Azure 1.0-preview(v1)+\r\n x_Credits = parse_json(x_Credits), // GCP Jan 2024\r\n x_CostType, // GCP Jan 2024\r\n x_CurrencyConversionRate, // GCP Jun 2024\r\n x_CustomerId, // Azure 1.0-preview(v1)+\r\n x_CustomerName, // Azure 1.0-preview(v1)+\r\n x_Discount = parse_json(x_Discount), // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice, // Azure 1.0-preview(v1)+\r\n x_ExportTime, // GCP Jan 2024\r\n x_IngestionTime, // Hubs add-on\r\n x_InvoiceId = coalesce(InvoiceId, x_InvoiceId), // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId = case( // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId == '-2', '',\r\n x_InvoiceSectionId\r\n ),\r\n x_InvoiceSectionName = case( // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName == 'Unassigned', '',\r\n x_InvoiceSectionName\r\n ),\r\n x_ListCostInUsd, // Azure 1.0-preview(v1)+\r\n x_Location, // GCP Jan 2024\r\n x_Operation, // AWS 1.0\r\n x_PartnerCreditApplied, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency = coalesce(PricingCurrency, x_PricingCurrency), // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription, // Azure 1.0-preview(v1)+\r\n x_Project, // GCP Jan 2024\r\n x_PublisherCategory, // Azure 1.0-preview(v1)+\r\n x_PublisherId, // Azure 1.0-preview(v1)+\r\n x_ResellerId, // Azure 1.0-preview(v1)+\r\n x_ResellerName, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName = tolower(x_ResourceGroupName), // Azure 1.0-preview(v1)+\r\n x_ResourceType, // Azure 1.0-preview(v1)+\r\n x_ServiceCode, // AWS 1.0\r\n x_ServiceId, // GCP Jan 2024\r\n x_ServicePeriodEnd, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart, // Azure 1.0-preview(v1)+\r\n x_SkuDescription, // Azure 1.0-preview(v1)+\r\n x_SkuDetails = parse_json(x_SkuDetails), // Azure 1.0-preview(v1)+\r\n x_SkuIsCreditEligible, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName = coalesce(SkuMeter, x_SkuMeterName), // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber, // Azure 1.0-preview(v1)+\r\n x_SkuRegion, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily, // Azure 1.0-preview(v1)+\r\n x_SkuTerm, // Azure 1.0-preview(v1)+\r\n x_SkuTier, // Azure 1.0-preview(v1)+\r\n x_SourceChanges, // Hubs add-on\r\n x_SourceName, // Hubs add-on\r\n x_SourceProvider, // Hubs add-on\r\n x_SourceType, // Hubs add-on\r\n x_SourceVersion, // Hubs add-on\r\n x_UsageType // AWS 1.0\r\n}\r\n\r\n// Costs_final_v1_0 table\r\n.create-merge table Costs_final_v1_0 (\r\n AvailabilityZone: string,\r\n BilledCost: decimal,\r\n BillingAccountId: string,\r\n BillingAccountName: string,\r\n BillingAccountType: string, // Azure 1.0-preview(v1)+\r\n BillingCurrency: string,\r\n BillingPeriodEnd: datetime,\r\n BillingPeriodStart: datetime,\r\n ChargeCategory: string,\r\n ChargeClass: string,\r\n ChargeDescription: string,\r\n ChargeFrequency: string,\r\n ChargePeriodEnd: datetime,\r\n ChargePeriodStart: datetime,\r\n CommitmentDiscountCategory: string, // FOCUS 1.0-preview only\r\n CommitmentDiscountId: string,\r\n CommitmentDiscountName: string,\r\n CommitmentDiscountStatus: string,\r\n CommitmentDiscountType: string,\r\n ConsumedQuantity: decimal,\r\n ConsumedUnit: string,\r\n ContractedCost: decimal,\r\n ContractedUnitPrice: decimal,\r\n EffectiveCost: decimal,\r\n InvoiceIssuerName: string,\r\n ListCost: decimal,\r\n ListUnitPrice: decimal,\r\n PricingCategory: string,\r\n PricingQuantity: decimal,\r\n PricingUnit: string,\r\n ProviderName: string,\r\n PublisherName: string,\r\n RegionId: string,\r\n RegionName: string,\r\n ResourceId: string,\r\n ResourceName: string,\r\n ResourceType: string,\r\n ServiceCategory: string,\r\n ServiceName: string,\r\n SkuId: string,\r\n SkuPriceId: string,\r\n SubAccountId: string,\r\n SubAccountName: string,\r\n SubAccountType: string,\r\n Tags: dynamic,\r\n x_AccountId: string, // Azure 1.0-preview(v1)+\r\n x_AccountName: string, // Azure 1.0-preview(v1)+\r\n x_AccountOwnerId: string, // Azure 1.0-preview(v1)+\r\n x_BilledCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_BilledUnitPrice: decimal, // Azure 1.0-preview(v1)+\r\n x_BillingAccountAgreement: string, // Hubs add-on\r\n x_BillingAccountId: string, // Azure 1.0-preview(v1)+\r\n x_BillingAccountName: string, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRate: decimal, // Azure 1.0-preview(v1)+\r\n x_BillingExchangeRateDate: datetime, // Azure 1.0-preview(v1)+\r\n x_BillingProfileId: string, // Azure 1.0-preview(v1)+\r\n x_BillingProfileName: string, // Azure 1.0-preview(v1)+\r\n x_ChargeId: string, // Azure 1.0-preview(v1) only\r\n x_ContractedCostInUsd: decimal, // Azure 1.0+\r\n x_CostAllocationRuleName: string, // Azure 1.0-preview(v1)+\r\n x_CostCategories: dynamic, // AWS 1.0 (JSON)\r\n x_CostCenter: string, // Azure 1.0-preview(v1)+\r\n x_Credits: dynamic, // GCP Jan 2024\r\n x_CostType: string, // GCP Jan 2024\r\n x_CurrencyConversionRate: decimal, // GCP Jun 2024\r\n x_CustomerId: string, // Azure 1.0-preview(v1)+\r\n x_CustomerName: string, // Azure 1.0-preview(v1)+\r\n x_Discount: dynamic, // AWS 1.0 (JSON)\r\n x_EffectiveCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_EffectiveUnitPrice: decimal, // Azure 1.0-preview(v1)+\r\n x_ExportTime: datetime, // GCP Jan 2024\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_InvoiceId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceIssuerId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionId: string, // Azure 1.0-preview(v1)+\r\n x_InvoiceSectionName: string, // Azure 1.0-preview(v1)+\r\n x_ListCostInUsd: decimal, // Azure 1.0-preview(v1)+\r\n x_Location: string, // GCP Jan 2024\r\n x_Operation: string, // AWS 1.0\r\n x_PartnerCreditApplied: string, // Azure 1.0-preview(v1)+\r\n x_PartnerCreditRate: string, // Azure 1.0-preview(v1)+\r\n x_PricingBlockSize: decimal, // Azure 1.0-preview(v1)+\r\n x_PricingCurrency: string, // Azure 1.0-preview(v1)+\r\n x_PricingSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_PricingUnitDescription: string, // Azure 1.0-preview(v1)+\r\n x_Project: string, // GCP Jan 2024\r\n x_PublisherCategory: string, // Azure 1.0-preview(v1)+\r\n x_PublisherId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerId: string, // Azure 1.0-preview(v1)+\r\n x_ResellerName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceGroupName: string, // Azure 1.0-preview(v1)+\r\n x_ResourceType: string, // Azure 1.0-preview(v1)+\r\n x_ServiceCode: string, // AWS 1.0\r\n x_ServiceId: string, // GCP Jan 2024\r\n x_ServicePeriodEnd: datetime, // Azure 1.0-preview(v1)+\r\n x_ServicePeriodStart: datetime, // Azure 1.0-preview(v1)+\r\n x_SkuDescription: string, // Azure 1.0-preview(v1)+\r\n x_SkuDetails: dynamic, // Azure 1.0-preview(v1)+\r\n x_SkuIsCreditEligible: bool, // Azure 1.0-preview(v1)+\r\n x_SkuMeterCategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterId: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterName: string, // Azure 1.0-preview(v1)+\r\n x_SkuMeterSubcategory: string, // Azure 1.0-preview(v1)+\r\n x_SkuOfferId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderId: string, // Azure 1.0-preview(v1)+\r\n x_SkuOrderName: string, // Azure 1.0-preview(v1)+\r\n x_SkuPartNumber: string, // Azure 1.0-preview(v1)+\r\n x_SkuRegion: string, // Azure 1.0-preview(v1)+\r\n x_SkuServiceFamily: string, // Azure 1.0-preview(v1)+\r\n x_SkuTerm: int, // Azure 1.0-preview(v1)+\r\n x_SkuTier: string, // Azure 1.0-preview(v1)+\r\n x_SourceChanges: string, // Hubs add-on\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_UsageType: string // AWS 1.0\r\n)\r\n\r\n// Update policy for Costs_raw -> Costs_final_v1_0 table\r\n.alter table Costs_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Costs_raw\",\r\n \"Query\": \"Costs_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Actual costs |===================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// ActualCosts_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use ActualCosts_transform_v1_2() instead.', folder='Costs')\r\nActualCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n // TODO: Transform actual costs to FOCUS 1.0 format\r\n ActualCosts_raw\r\n | where ChargeType in ('Purchase', 'Refund') and isnotempty(ReservationId)\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodStart = Date,\r\n ChargePeriodEnd = Date + 1d,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = '',\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory = '',\r\n SkuId = '',\r\n SkuMeter = '',\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = SubscriptionName,\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentName = '',\r\n x_ComponentType = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel = '',\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for ActualCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"ActualCosts_raw\",\r\n \"Query\": \"ActualCosts_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Amortized costs |================================================================================================\r\n// Supported versions:\r\n// - C360-2025-04\r\n//======================================================================================================================\r\n\r\n// AmortizedCosts_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: ActualCost exports transformed to FOCUS 1.0. Use AmortizedCosts_transform_v1_2() instead.', folder='Costs')\r\nAmortizedCosts_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n // TODO: Transform actual costs to FOCUS 1.0 format\r\n AmortizedCosts_raw\r\n //\r\n //\r\n // !!! The rest of the query is reused for both the ActualCosts and AmortizedCosts queries -- Copy all changes in both transform functions !!!\r\n //\r\n //\r\n | extend x_AmortizationClass = case(\r\n ChargeType in ('Purchase', 'Refund') and (tolower(ResourceId) contains '/microsoft.capacity/reservationorders/' or tolower(ResourceId) contains '/microsoft.billingbenefits/savingsplanorders/'), 'Principal',\r\n ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', 'Amortized Charge',\r\n ''\r\n )\r\n | extend tmp_ResourceInfo = parse_resourceid(ResourceId)\r\n // TODO: PricingCategory needs to include savings plan usage and spot usage\r\n | extend PricingCategory = case(\r\n x_AmortizationClass == 'Amortized Charge', 'Committed',\r\n ChargeType in ('Usage', 'Purchase'), 'Standard',\r\n ''\r\n )\r\n | extend x_ResourceType = tostring(tmp_ResourceInfo.x_ResourceType)\r\n | project-rename\r\n PricingQuantity = Quantity,\r\n x_PricingUnitDescription = UnitOfMeasure\r\n | join kind=leftouter (PricingUnits) on x_PricingUnitDescription\r\n | join kind=leftouter (Regions) on ResourceLocation\r\n | join kind=leftouter (ResourceTypes | project x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n // TODO: Add the following in 1.2: ServiceSubcategory, PublisherName, x_PublisherCategory, x_Environment, x_ServiceModel\r\n | join kind=leftouter (Services | where isnotempty(x_ResourceType) | project x_ResourceType, ServiceName, ServiceCategory) on x_ResourceType\r\n | join kind=leftouter (Services | where isnotempty(x_ConsumedService) | summarize take_any(ServiceName), take_any(ServiceCategory) by ConsumedService = x_ConsumedService) on ConsumedService\r\n | extend CommitmentDiscountCategory = iff(isnotempty(ReservationId), 'Usage', '') // TODO: CommitmentDiscountCategory needs to handle savings plans\r\n | project\r\n AvailabilityZone = AvailabilityZone,\r\n BilledCost = iff(x_AmortizationClass == 'Amortized Charge', real(0), Cost),\r\n BillingAccountId = strcat('/providers/microsoft.billing/billingaccounts/', BillingAccountId, iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), '', strcat('/billingprofiles/', BillingProfileId))),\r\n BillingAccountName = coalesce(BillingProfileName, BillingAccountName),\r\n BillingAccountType = iff(BillingAccountId == BillingProfileId or isempty(BillingProfileId), 'Billing Profile', 'Billing Account'),\r\n BillingCurrency,\r\n BillingPeriodEnd = startofmonth(BillingPeriodEndDate, 1),\r\n BillingPeriodStart = startofmonth(BillingPeriodStartDate),\r\n CapacityReservationId = '',\r\n CapacityReservationStatus = '',\r\n ChargeCategory = case(\r\n ChargeType in ('Usage', 'Purchase', 'Credit', 'Tax'), ChargeType,\r\n ChargeType in ('UnusedReservation', 'UnusedSavingsPlan'), 'Usage',\r\n ChargeType == 'Refund', 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = iff(ChargeType == 'Refund', 'Correction', ''),\r\n ChargeDescription = Product,\r\n ChargeFrequency = case(\r\n Frequency == 'UsageBased', 'Usage-Based',\r\n Frequency == 'OneTime', 'One-Time',\r\n Frequency // \"Recurring\" and any fallback\r\n ),\r\n ChargePeriodStart = Date,\r\n ChargePeriodEnd = Date + 1d,\r\n ChargeSubcategory = '',\r\n // TODO: CommitmentDiscount* columns need to handle savings plans\r\n CommitmentDiscountCategory,\r\n CommitmentDiscountId = iff(isnotempty(ReservationId), strcat('/providers/microsoft.capacity/reservationorders/', ProductOrderId, '/reservations/', ReservationId), ''),\r\n CommitmentDiscountName = ReservationName,\r\n CommitmentDiscountQuantity = real(null),\r\n CommitmentDiscountStatus = case(\r\n isempty(ReservationId), '',\r\n isnotempty(ReservationId) and ChargeType == 'Usage', 'Used',\r\n ChargeType startswith 'Unused', 'Unused',\r\n 'Unused'\r\n ),\r\n CommitmentDiscountType = iff(isnotempty(ReservationId), 'Reservation', ''),\r\n CommitmentDiscountUnit = '',\r\n ConsumedQuantity = PricingQuantity * x_PricingBlockSize,\r\n ConsumedUnit = PricingUnit,\r\n ContractedCost = UnitPrice * PricingQuantity,\r\n ContractedUnitPrice = UnitPrice,\r\n EffectiveCost = iff(x_AmortizationClass == 'Principal', real(0), Cost),\r\n InvoiceId = '',\r\n InvoiceIssuerName = 'Microsoft',\r\n ListCost = real(null),\r\n ListUnitPrice = real(null),\r\n PricingCategory,\r\n PricingCurrency = '',\r\n PricingQuantity,\r\n PricingUnit,\r\n ProviderName = 'Microsoft',\r\n PublisherName = iff(PublisherType == 'Marketplace', PublisherName, 'Microsoft'),\r\n Region = '',\r\n RegionId,\r\n RegionName,\r\n ResourceId = tostring(tmp_ResourceInfo.ResourceId),\r\n ResourceName = tostring(tmp_ResourceInfo.ResourceName),\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n ServiceSubcategory = '',\r\n SkuId = '',\r\n SkuMeter = '',\r\n SkuPriceDetails = '',\r\n SkuPriceId = '',\r\n SubAccountId = strcat('/subscriptions/', SubscriptionId),\r\n SubAccountName = SubscriptionName,\r\n SubAccountType = iff(isempty(SubscriptionId), '', 'Subscription'),\r\n Tags = iff(Tags startswith '{', Tags, strcat('{', Tags, '}')),\r\n UsageAmount = real(null),\r\n UsageQuantity = real(null),\r\n UsageUnit = '',\r\n x_AccountId = '',\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerId,\r\n x_AmortizationClass = '',\r\n x_BilledCostInUsd = real(null),\r\n x_BilledUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), UnitPrice),\r\n x_BillingAccountId = BillingAccountId,\r\n x_BillingAccountName = BillingAccountName,\r\n x_BillingExchangeRate = real(null),\r\n x_BillingExchangeRateDate = datetime(null),\r\n x_BillingProfileId = BillingProfileId,\r\n x_BillingProfileName = BillingProfileName,\r\n x_BillingItemCode = '',\r\n x_BillingItemName = '',\r\n x_ChargeId = '',\r\n x_CommodityCode = '',\r\n x_CommodityName = '',\r\n x_ComponentName = '',\r\n x_ComponentType = '',\r\n x_ContractedCostInUsd = real(null),\r\n x_Cost = real(null),\r\n x_CostAllocationRuleName = '',\r\n x_CostCategories = '',\r\n x_CostCenter = CostCenter,\r\n x_CostType = '',\r\n x_Credits = '',\r\n x_CurrencyConversionRate = real(null),\r\n x_CustomerId = '',\r\n x_CustomerName = '',\r\n x_Discount = '',\r\n x_EffectiveCostInUsd = real(null),\r\n x_EffectiveUnitPrice = iff(ChargeType == 'Usage' and isnotempty(ReservationId) and ChargeType != 'UnusedSavingsPlan', real(0), EffectivePrice),\r\n x_ExportTime = datetime(null),\r\n x_InstanceID = '',\r\n x_InvoiceId = '',\r\n x_InvoiceIssuerId = '',\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = InvoiceSection,\r\n x_ListCostInUsd = real(null),\r\n x_Location = '',\r\n x_OnDemandCost = real(null),\r\n x_OnDemandCostInUsd = real(null),\r\n x_OnDemandUnitPrice = real(null),\r\n x_Operation = '',\r\n x_OwnerAccountID = '',\r\n x_PartnerCreditApplied = '',\r\n x_PartnerCreditRate = '',\r\n x_PricingBlockSize,\r\n x_PricingCurrency = iff(BillingAccountId == BillingProfileId, BillingCurrency, ''),\r\n x_PricingSubcategory = case(\r\n // TODO: Add x_SkuTier when supported by C360 -- PricingCategory == 'Standard' and isnotempty(x_SkuTier), 'Tiered',\r\n PricingCategory == 'Standard', 'Standard',\r\n PricingCategory == 'Committed', strcat('Committed ', CommitmentDiscountCategory),\r\n PricingCategory == 'Dynamic', 'Spot',\r\n ''\r\n ),\r\n x_PricingUnitDescription,\r\n x_Project = '',\r\n x_PublisherCategory = iff(PublisherType == 'Marketplace', 'Vendor', 'Cloud Provider'),\r\n x_PublisherId = '',\r\n x_ResellerId = '',\r\n x_ResellerName = '',\r\n x_ResourceGroupName = ResourceGroup,\r\n x_ResourceType,\r\n x_ServiceCode = '',\r\n x_ServiceId = '',\r\n x_ServiceModel = '',\r\n x_ServicePeriodEnd = datetime(null),\r\n x_ServicePeriodStart = datetime(null),\r\n x_SkuDescription = Product,\r\n x_SkuDetails = iff(AdditionalInfo startswith '{', AdditionalInfo, strcat('{', AdditionalInfo, '}')),\r\n x_SkuIsCreditEligible = IsAzureCreditEligible,\r\n x_SkuMeterCategory = MeterCategory,\r\n x_SkuMeterId = MeterId,\r\n x_SkuMeterName = MeterName,\r\n x_SkuMeterSubcategory = MeterSubCategory,\r\n x_SkuOfferId = OfferId,\r\n x_SkuOrderId = ProductOrderId,\r\n x_SkuOrderName = ProductOrderName,\r\n x_SkuPartNumber = PartNumber,\r\n x_SkuPlanName = '',\r\n x_SkuRegion = MeterRegion,\r\n x_SkuServiceFamily = ServiceFamily,\r\n x_SkuTerm = toint(Term),\r\n x_SkuTier = '',\r\n x_SourceName = 'C360',\r\n x_SourceProvider = 'Microsoft',\r\n x_SourceType = 'ActualCost',\r\n x_SourceVersion = 'C360-2025-04',\r\n x_SubproductName = '',\r\n x_UsageType = ''\r\n}\r\n\r\n// Update policy for AmortizedCosts_raw -> Costs_raw table\r\n.alter table Costs_raw policy update\r\n``` \r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"AmortizedCosts_raw\",\r\n \"Query\": \"AmortizedCosts_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| CommitmentDiscountUsage |========================================================================================\r\n// Supported versions:\r\n// - MS EA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-ea\r\n// - MS MCA reservation details: 2023-03-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-details-mca\r\n//======================================================================================================================\r\n\r\n// CommitmentDiscountUsage_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All commitment discount usage transformed to FOCUS 1.0. This includes reservationdeatils_raw. Use CommitmentDiscountUsage_transform_v1_2() instead.', folder='Commitment discounts')\r\nCommitmentDiscountUsage_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n CommitmentDiscountUsage_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\r\n ReservedHours = todecimal(ReservedHours),\r\n TotalReservedQuantity = todecimal(TotalReservedQuantity),\r\n UsedHours = todecimal(UsedHours)\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Handle resource columns\r\n | extend ResourceId = tolower(InstanceId)\r\n | extend tmp_ResourceDetails = parse_resourceid(ResourceId)\r\n | extend ResourceName = tostring(tmp_ResourceDetails.ResourceName)\r\n | extend SubAccountId = tostring(tmp_ResourceDetails.SubAccountId)\r\n | extend x_ResourceGroupName = tostring(tmp_ResourceDetails.x_ResourceGroupName)\r\n | extend x_ResourceType = tostring(tmp_ResourceDetails.x_ResourceType)\r\n | lookup kind=leftouter (ResourceTypes | distinct x_ResourceType, ResourceType = SingularDisplayName) on x_ResourceType\r\n | lookup kind=leftouter (Services | distinct x_ResourceType, ServiceName, ServiceCategory, x_ServiceModel) on x_ResourceType\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n ChargePeriodEnd = UsageDate + 1d,\r\n ChargePeriodStart = UsageDate,\r\n CommitmentDiscountCategory = 'Usage',\r\n CommitmentDiscountId = tolower(strcat('/providers/microsoft.capacity/reservationorders/', ReservationOrderId, '/reservations/', ReservationId)),\r\n CommitmentDiscountType = 'Reservation',\r\n ConsumedQuantity = UsedHours,\r\n ProviderName,\r\n ResourceId,\r\n ResourceName,\r\n ResourceType,\r\n ServiceCategory,\r\n ServiceName,\r\n SubAccountId,\r\n x_CommitmentDiscountCommittedCount = TotalReservedQuantity,\r\n x_CommitmentDiscountCommittedAmount = ReservedHours,\r\n // TODO: Is this needed? -- x_CommitmentDiscountKind = Kind,\r\n x_CommitmentDiscountNormalizedGroup = iff(InstanceFlexibilityGroup == 'NA', '', InstanceFlexibilityGroup),\r\n x_CommitmentDiscountNormalizedRatio = InstanceFlexibilityRatio,\r\n x_CommitmentDiscountQuantity = UsedHours * InstanceFlexibilityRatio,\r\n x_IngestionTime = ingestion_time(),\r\n x_ResourceGroupName,\r\n x_ResourceType,\r\n // x_RowId = hash_sha256(strcat(\r\n // // DO NOT CHANGE COLUMNS OR COLUMN ORDER\r\n // CommitmentDiscountId,\r\n // ResourceId,\r\n // ChargePeriodStart\r\n // )),\r\n x_ServiceModel,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuSize = iff(SkuName == 'NA', '', SkuName),\r\n x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName)),\r\n x_SourceProvider = coalesce(x_SourceProvider, ProviderName),\r\n x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationDetails', '')),\r\n x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2024-03-01', ''))\r\n}\r\n\r\n// CommitmentDiscountUsage_final_v1_0 table\r\n.create-merge table CommitmentDiscountUsage_final_v1_0 (\r\n ChargePeriodEnd: datetime, // Hubs add-on\r\n ChargePeriodStart: datetime, // MS 2023-03-01\r\n CommitmentDiscountCategory: string, // Hubs add-on\r\n CommitmentDiscountId: string, // MS 2023-03-01\r\n CommitmentDiscountType: string, // Hubs add-on\r\n ConsumedQuantity: decimal, // MS 2023-03-01\r\n ProviderName: string, // Hubs add-on\r\n ResourceId: string, // MS 2023-03-01\r\n ResourceName: string, // Hubs add-on\r\n ResourceType: string, // Hubs add-on\r\n ServiceCategory: string, // Hubs add-on\r\n ServiceName: string, // Hubs add-on\r\n SubAccountId: string, // Hubs add-on\r\n x_CommitmentDiscountCommittedCount: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountCommittedAmount: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedGroup: string, // MS 2023-03-01\r\n x_CommitmentDiscountNormalizedRatio: decimal, // MS 2023-03-01\r\n x_CommitmentDiscountQuantity: decimal, // MS 2023-03-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_ResourceGroupName: string, // Hubs add-on\r\n x_ResourceType: string, // Hubs add-on\r\n x_ServiceModel: string, // Hubs add-on\r\n x_SkuOrderId: string, // MS 2023-03-01\r\n x_SkuSize: string, // MS 2023-03-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string // Hubs add-on\r\n)\r\n\r\n// Update policy for CommitmentDiscountUsage_raw -> CommitmentDiscountUsage_final_v1_0 table\r\n.alter table CommitmentDiscountUsage_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"CommitmentDiscountUsage_raw\",\r\n \"Query\": \"CommitmentDiscountUsage_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Recommendations |================================================================================================\r\n// Supported datasets/versions:\r\n// - MS CM EA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-ea\r\n// - MS CM MCA reservation recommendations: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-recommendations-mca\r\n//======================================================================================================================\r\n\r\n// Recommendations_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All recommendations transformed to FOCUS 1.0. Use Recommendations_transform_v1_2() instead.', folder='Recommendations')\r\nRecommendations_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Recommendations_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n CostWithNoReservedInstances = todecimal(CostWithNoReservedInstances),\r\n InstanceFlexibilityRatio = todecimal(InstanceFlexibilityRatio),\r\n NetSavings = todecimal(NetSavings),\r\n RecommendedQuantity = todecimal(RecommendedQuantity),\r\n RecommendedQuantityNormalized = todecimal(RecommendedQuantityNormalized),\r\n TotalCostWithReservedInstances = todecimal(TotalCostWithReservedInstances)\r\n //\r\n | extend x_IngestionTime = ingestion_time()\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationRecommendations', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Convert JSON cost columns to decimal\r\n | extend CostWithNoReservedInstances = case(isnotempty(CostWithNoReservedInstances), CostWithNoReservedInstances, isnotempty(CostWithNoReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, CostWithNoReservedInstancesJson)), CostWithNoReservedInstances)\r\n | extend NetSavings = case(isnotempty(NetSavings), NetSavings, isnotempty(NetSavingsJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, NetSavingsJson)), NetSavings)\r\n | extend TotalCostWithReservedInstances = case(isnotempty(TotalCostWithReservedInstances), TotalCostWithReservedInstances, isnotempty(TotalCostWithReservedInstancesJson), todecimal(extract(@'\"value\":([0-9\\.]+)', 1, TotalCostWithReservedInstancesJson)), TotalCostWithReservedInstances)\r\n //\r\n // Build recommendation details\r\n | lookup kind=leftouter (Regions | summarize RegionName = make_set(RegionName)[0] by Location = RegionId) on Location\r\n | extend x_RecommendationDetails = case(\r\n x_SourceType == 'ReservationRecommendations', bag_pack(\r\n 'CommitmentDiscountNormalizedGroup', InstanceFlexibilityGroup,\r\n 'CommitmentDiscountNormalizedRatio', InstanceFlexibilityRatio,\r\n 'CommitmentDiscountNormalizedSize', NormalizedSize,\r\n 'CommitmentDiscountResourceType', ResourceType,\r\n 'CommitmentDiscountScope', Scope,\r\n 'LookbackPeriodDuration', case(\r\n LookBackPeriod matches regex @'^Last([0-9]+)Days$', replace_regex(LookBackPeriod, @'^Last([0-9]+)Days$', @'P\\1D'),\r\n LookBackPeriod matches regex @'^[0-9]+$', strcat('P', LookBackPeriod, 'D'),\r\n ''\r\n ),\r\n 'LookbackPeriodStart', FirstUsageDate,\r\n 'RecommendedQuantity', RecommendedQuantity,\r\n 'RecommendedQuantityNormalized', RecommendedQuantityNormalized,\r\n 'RegionId', Location,\r\n 'RegionName', RegionName,\r\n 'SkuMeterId', MeterId,\r\n 'SkuPriceDetails', SkuProperties,\r\n 'SkuSize', coalesce(SKU, SkuName),\r\n 'SkuTerm', isoMonths(Term)\r\n ),\r\n dynamic({})\r\n )\r\n //\r\n // Sort columns and apply final transforms\r\n | extend x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d)\r\n | extend x_RecommendationDate = iff(x_RecommendationDate > now(), startofday(now()), x_RecommendationDate)\r\n | project\r\n ProviderName,\r\n SubAccountId = iff(isnotempty(SubscriptionId), strcat('/subscriptions/', SubscriptionId), ''),\r\n x_IngestionTime,\r\n x_EffectiveCostAfter = TotalCostWithReservedInstances,\r\n x_EffectiveCostBefore = CostWithNoReservedInstances,\r\n x_EffectiveCostSavings = NetSavings,\r\n x_RecommendationDate = FirstUsageDate + (toint(extract(@'^P([0-9]+)D$', 1, tostring(x_RecommendationDetails.LookbackPeriodDuration))) * 1d),\r\n x_RecommendationDetails,\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion\r\n}\r\n\r\n// Recommendations_final_v1_0 table\r\n.create-merge table Recommendations_final_v1_0 (\r\n ProviderName: string,\r\n SubAccountId: string,\r\n x_IngestionTime: datetime,\r\n x_EffectiveCostAfter: decimal,\r\n x_EffectiveCostBefore: decimal,\r\n x_EffectiveCostSavings: decimal,\r\n x_RecommendationDate: datetime,\r\n x_RecommendationDetails: dynamic,\r\n x_SourceName: string,\r\n x_SourceProvider: string,\r\n x_SourceType: string,\r\n x_SourceVersion: string\r\n)\r\n\r\n// Update policy for Recommendations_raw -> Recommendations_final_v1_0 table\r\n.alter table Recommendations_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Recommendations_raw\",\r\n \"Query\": \"Recommendations_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n\r\n\r\n//===| Transactions |===================================================================================================\r\n// Supported versions:\r\n// - MS CM EA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-ea\r\n// - MS CM MCA reservation transactions: 2023-05-01 -- See https://learn.microsoft.com/azure/cost-management-billing/dataset-schema/reservation-transactions-mca\r\n//======================================================================================================================\r\n\r\n// Transactions_transform_v1_0 function\r\n.create-or-alter function\r\nwith (docstring='DEPRECATED: All transactions transformed to FOCUS 1.0. Use Transactions_transform_v1_2() instead.', folder='Transactions')\r\nTransactions_transform_v1_0()\r\n{\r\n // NOTE: All open issues and questions are tracked @ https://github.com/microsoft/finops-toolkit/issues/1111\r\n let isoMonths = (duration: string) {\r\n let number = toint(replace_regex(duration, @'[PMY]', ''));\r\n toint(case(\r\n duration == '', toint(''),\r\n duration endswith \"Y\", number * 12,\r\n duration endswith \"M\", number,\r\n -1\r\n ))\r\n };\r\n Transactions_raw\r\n //\r\n // Change real to decimal\r\n | extend\r\n Amount = todecimal(Amount),\r\n MonetaryCommitment = todecimal(MonetaryCommitment),\r\n Overage = todecimal(Overage),\r\n Quantity = todecimal(Quantity)\r\n //\r\n // Set ProviderName\r\n | extend ProviderName = 'Microsoft'\r\n //\r\n // Set source columns\r\n | extend x_SourceName = coalesce(x_SourceName, iff(ProviderName == 'Microsoft', 'Cost Management', ProviderName))\r\n | extend x_SourceProvider = coalesce(x_SourceProvider, ProviderName)\r\n | extend x_SourceType = coalesce(x_SourceType, iff(ProviderName == 'Microsoft', 'ReservationTransactions', ''))\r\n | extend x_SourceVersion = coalesce(x_SourceVersion, iff(ProviderName == 'Microsoft', '2023-05-01', ''))\r\n //\r\n // Handle BillingPeriodStart/End\r\n | extend BillingMonth = tostring(BillingMonth)\r\n | extend BillingPeriodStart = iff(isempty(BillingMonth), datetime(null), todatetime(strcat(substring(BillingMonth, 0, 4), \"-\", substring(BillingMonth, 4, 2), \"-\", substring(BillingMonth, 6, 2))))\r\n | extend BillingPeriodEnd = iff(isempty(BillingMonth), datetime(null), startofmonth(endofmonth(BillingPeriodStart) + 1d))\r\n //\r\n // Sort columns and apply final transforms\r\n | project\r\n BilledCost = Amount,\r\n BillingAccountId = case(\r\n BillingProfileId startswith '/', BillingProfileId,\r\n isnotempty(CurrentEnrollmentId), strcat('/providers/Microsoft.Billing/billingAccounts/', CurrentEnrollmentId),\r\n isnotempty(BillingProfileId), strcat('/providers/Microsoft.Billing/billingProfiles/', BillingProfileId),\r\n ''\r\n ),\r\n BillingAccountName = coalesce(BillingProfileName, CurrentEnrollmentId),\r\n BillingCurrency = Currency,\r\n BillingPeriodEnd,\r\n BillingPeriodStart,\r\n ChargeCategory = case(\r\n EventType in ('Cancel', 'Purchase', 'Refund'), 'Purchase',\r\n 'Adjustment'\r\n ),\r\n ChargeClass = case(\r\n EventType == 'Cancel', 'Cancel', // FOCUS does not handle this scenario\r\n EventType == 'Refund', 'Correction',\r\n ''\r\n ),\r\n ChargeDescription = Description,\r\n ChargeFrequency = case(\r\n BillingFrequency == 'OneTime', 'One-Time',\r\n BillingFrequency == 'Recurring', 'Recurring',\r\n BillingFrequency\r\n ),\r\n ChargePeriodStart = EventDate,\r\n PricingQuantity = Quantity,\r\n PricingUnit = 'Reservations',\r\n ProviderName,\r\n RegionId = Region,\r\n RegionName = Region,\r\n SubAccountId = iff(isempty(PurchasingSubscriptionGuid), '', strcat('/subscriptions/', PurchasingSubscriptionGuid)),\r\n SubAccountName = iff(isempty(PurchasingSubscriptionGuid), '', PurchasingSubscriptionName),\r\n x_AccountName = AccountName,\r\n x_AccountOwnerId = AccountOwnerEmail,\r\n x_CostCenter = CostCenter,\r\n x_InvoiceId = InvoiceId,\r\n x_InvoiceNumber = Invoice,\r\n x_InvoiceSectionId = InvoiceSectionId,\r\n x_InvoiceSectionName = coalesce(InvoiceSectionName, DepartmentName),\r\n x_IngestionTime = ingestion_time(),\r\n x_MonetaryCommitment = MonetaryCommitment,\r\n x_Overage = Overage,\r\n x_PurchasingBillingAccountId = PurchasingEnrollment,\r\n x_SkuOrderId = ReservationOrderId,\r\n x_SkuOrderName = ReservationOrderName,\r\n x_SkuSize = ArmSkuName,\r\n x_SkuTerm = isoMonths(Term),\r\n x_SourceName,\r\n x_SourceProvider,\r\n x_SourceType,\r\n x_SourceVersion,\r\n x_SubscriptionId = PurchasingSubscriptionGuid,\r\n x_TransactionType = EventType\r\n}\r\n\r\n// Transactions_final_v1_0 table\r\n.create-merge table Transactions_final_v1_0 (\r\n BilledCost: decimal, // MS CM EA+MCA 2023-05-01\r\n BillingAccountId: string, // MS CM EA+MCA 2023-05-01\r\n BillingAccountName: string, // MS CM EA+MCA 2023-05-01\r\n BillingCurrency: string, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodEnd: datetime, // MS CM EA+MCA 2023-05-01\r\n BillingPeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n ChargeCategory: string, // Hubs add-on\r\n ChargeClass: string, // Hubs add-on\r\n ChargeDescription: string, // MS CM EA+MCA 2023-05-01\r\n ChargeFrequency: string, // MS CM EA+MCA 2023-05-01\r\n ChargePeriodStart: datetime, // MS CM EA+MCA 2023-05-01\r\n PricingQuantity: decimal, // MS CM EA+MCA 2023-05-01\r\n PricingUnit: string, // Hubs add-on\r\n ProviderName: string, // Hubs add-on\r\n RegionId: string, // MS CM EA+MCA 2023-05-01\r\n RegionName: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountId: string, // MS CM EA+MCA 2023-05-01\r\n SubAccountName: string, // MS CM EA+MCA 2023-05-01\r\n x_AccountName: string, // MS CM EA 2023-05-01\r\n x_AccountOwnerId: string, // MS CM EA 2023-05-01\r\n x_CostCenter: string, // MS CM EA 2023-05-01\r\n x_InvoiceId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceNumber: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionId: string, // MS CM MCA 2023-05-01\r\n x_InvoiceSectionName: string, // MS CM MCA 2023-05-01\r\n x_IngestionTime: datetime, // Hubs add-on\r\n x_MonetaryCommitment: decimal, // MS CM EA 2023-05-01\r\n x_Overage: decimal, // MS CM EA 2023-05-01\r\n x_PurchasingBillingAccountId: string, // MS CM EA 2023-05-01\r\n x_SkuOrderId: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuOrderName: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuSize: string, // MS CM EA+MCA 2023-05-01\r\n x_SkuTerm: int, // MS CM EA+MCA 2023-05-01\r\n x_SourceName: string, // Hubs add-on\r\n x_SourceProvider: string, // Hubs add-on\r\n x_SourceType: string, // Hubs add-on\r\n x_SourceVersion: string, // Hubs add-on\r\n x_SubscriptionId: string, // MS CM EA+MCA 2023-05-01\r\n x_TransactionType: string // MS CM EA+MCA 2023-05-01\r\n)\r\n\r\n// Update policy for Transactions_raw -> Transactions_final_v1_0 table\r\n.alter table Transactions_final_v1_0 policy update\r\n```\r\n[{\r\n \"IsEnabled\": false,\r\n \"Source\": \"Transactions_raw\",\r\n \"Query\": \"Transactions_transform_v1_0()\",\r\n \"IsTransactional\": true,\r\n \"PropagateIngestionProperties\": true\r\n}]\r\n```\r\n", + "dataExplorerPrivateDnsZoneName": "[replace(format('privatelink.{0}.{1}', parameters('location'), replace(environment().suffixes.storage, 'core', 'kusto')), '..', '.')]", + "ingestionCapacity": { + "Dev(No SLA)_Standard_E2a_v4": 1, + "Dev(No SLA)_Standard_D11_v2": 1, + "Standard_D11_v2": 2, + "Standard_D12_v2": 4, + "Standard_D13_v2": 8, + "Standard_D14_v2": 16, + "Standard_D16d_v5": 16, + "Standard_D32d_v4": 32, + "Standard_D32d_v5": 32, + "Standard_DS13_v2+1TB_PS": 8, + "Standard_DS13_v2+2TB_PS": 8, + "Standard_DS14_v2+3TB_PS": 16, + "Standard_DS14_v2+4TB_PS": 16, + "Standard_E2a_v4": 2, + "Standard_E2ads_v5": 2, + "Standard_E2d_v4": 2, + "Standard_E2d_v5": 2, + "Standard_E4a_v4": 4, + "Standard_E4ads_v5": 4, + "Standard_E4d_v4": 4, + "Standard_E4d_v5": 4, + "Standard_E8a_v4": 8, + "Standard_E8ads_v5": 8, + "Standard_E8as_v4+1TB_PS": 8, + "Standard_E8as_v4+2TB_PS": 8, + "Standard_E8as_v5+1TB_PS": 8, + "Standard_E8as_v5+2TB_PS": 8, + "Standard_E8d_v4": 8, + "Standard_E8d_v5": 8, + "Standard_E8s_v4+1TB_PS": 8, + "Standard_E8s_v4+2TB_PS": 8, + "Standard_E8s_v5+1TB_PS": 8, + "Standard_E8s_v5+2TB_PS": 8, + "Standard_E16a_v4": 16, + "Standard_E16ads_v5": 16, + "Standard_E16as_v4+3TB_PS": 16, + "Standard_E16as_v4+4TB_PS": 16, + "Standard_E16as_v5+3TB_PS": 16, + "Standard_E16as_v5+4TB_PS": 16, + "Standard_E16d_v4": 16, + "Standard_E16d_v5": 16, + "Standard_E16s_v4+3TB_PS": 16, + "Standard_E16s_v4+4TB_PS": 16, + "Standard_E16s_v5+3TB_PS": 16, + "Standard_E16s_v5+4TB_PS": 16, + "Standard_E64i_v3": 64, + "Standard_E80ids_v4": 80, + "Standard_EC8ads_v5": 8, + "Standard_EC8as_v5+1TB_PS": 8, + "Standard_EC8as_v5+2TB_PS": 8, + "Standard_EC16ads_v5": 16, + "Standard_EC16as_v5+3TB_PS": 16, + "Standard_EC16as_v5+4TB_PS": 16, + "Standard_L4s": 4, + "Standard_L8as_v3": 8, + "Standard_L8s": 8, + "Standard_L8s_v2": 8, + "Standard_L8s_v3": 8, + "Standard_L16as_v3": 16, + "Standard_L16s": 16, + "Standard_L16s_v2": 16, + "Standard_L16s_v3": 16, + "Standard_L32as_v3": 32, + "Standard_L32s_v3": 32 + } + }, + "resources": [ + { + "type": "Microsoft.Kusto/clusters/principalAssignments", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('clusterName'), 'adf-mi-cluster-admin')]", + "properties": { + "principalType": "App", + "principalId": "[reference(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), '2018-06-01', 'full').identity.principalId]", + "tenantId": "[reference(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), '2018-06-01', 'full').identity.tenantId]", + "role": "AllDatabasesAdmin" + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + ] + }, + { + "type": "Microsoft.Kusto/clusters/databases", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('clusterName'), 'Ingestion')]", + "location": "[parameters('location')]", + "kind": "ReadWrite", + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + ] + }, + { + "type": "Microsoft.Kusto/clusters/databases", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('clusterName'), 'Hub')]", + "location": "[parameters('location')]", + "kind": "ReadWrite", + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + ] + }, + { + "type": "Microsoft.Kusto/clusters", + "apiVersion": "2023-08-15", + "name": "[parameters('clusterName')]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Kusto/clusters'), createObject()))]", + "sku": { + "name": "[parameters('clusterSku')]", + "tier": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 'Basic', 'Standard')]", + "capacity": "[if(startsWith(parameters('clusterSku'), 'Dev(No SLA)_'), 1, if(equals(parameters('clusterCapacity'), 1), 2, parameters('clusterCapacity')))]" + }, + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "enableStreamingIngest": true, + "enableAutoStop": false, + "publicNetworkAccess": "[if(parameters('enablePublicAccess'), 'Enabled', 'Disabled')]" + } + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(parameters('clusterName'), subscription().id, 'Storage Blob Data Contributor')]", + "properties": { + "description": "Give \"Storage Blob Data Contributor\" to the cluster", + "principalId": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15', 'full').identity.principalId]", + "principalType": "ServicePrincipal", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + ] + }, + { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[variables('dataExplorerPrivateDnsZoneName')]", + "location": "global", + "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateDnsZones'), createObject()))]", + "properties": {} + }, + { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', variables('dataExplorerPrivateDnsZoneName'), format('{0}-link', replace(variables('dataExplorerPrivateDnsZoneName'), '.', '-')))]", + "location": "global", + "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks'), createObject()))]", + "properties": { + "virtualNetwork": { + "id": "[parameters('virtualNetworkId')]" + }, + "registrationEnabled": false + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" + ] + }, + { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', parameters('clusterName'))]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.Network/privateEndpoints'), createObject()))]", + "properties": { + "subnet": { + "id": "[parameters('privateEndpointSubnetId')]" + }, + "privateLinkServiceConnections": [ + { + "name": "dataExplorerLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "groupIds": [ + "cluster" + ] } } - ], - "parameters": { - "folderPath": { - "type": "string" - }, - "fileName": { - "type": "string" + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + ] + }, + { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', parameters('clusterName')), 'dataExplorer-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "privatelink-westus-kusto-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" + } }, - "originalFileName": { - "type": "string" + { + "name": "privatelink-blob-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } }, - "ingestionId": { - "type": "string" + { + "name": "privatelink-table-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.table.{0}', environment().suffixes.storage))]" + } }, - "table": { - "type": "string" + { + "name": "privatelink-queue-core-windows-net", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.queue.{0}', environment().suffixes.storage))]" + } } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/privateEndpoints', format('{0}-ep', parameters('clusterName')))]", + "[resourceId('Microsoft.Network/privateDnsZones', variables('dataExplorerPrivateDnsZoneName'))]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ingestion_OpenDataInternalScripts", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" }, - "variables": { - "tryAgain": { - "type": "Boolean", - "defaultValue": true + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('clusterName')]" }, - "logRetentionDays": { - "type": "Integer", - "defaultValue": 0 + "databaseName": { + "value": "Ingestion" }, - "finalRetentionMonths": { - "type": "Integer", - "defaultValue": 999 + "scripts": { + "value": { + "OpenDataFunctions_resource_type_1": "[variables('$fxv#0')]", + "OpenDataFunctions_resource_type_2": "[variables('$fxv#1')]", + "OpenDataFunctions_resource_type_3": "[variables('$fxv#2')]", + "OpenDataFunctions_resource_type_4": "[variables('$fxv#3')]", + "OpenDataFunctions_resource_type_5": "[variables('$fxv#4')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, - "annotations": [] + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" + } + }, + "parameters": { + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer instance." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + } + } + }, + "resources": [ + { + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" + }, + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" + } + } + ] + } }, "dependsOn": [ - "appRegistration", - "linkedService_dataExplorer" - ], - "metadata": { - "description": "Ingests parquet data into an Azure Data Explorer cluster." - } + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]" + ] }, - "pipeline_ExecuteIngestionETL": { - "condition": "[or(variables('useAzure'), variables('useFabric'))]", - "type": "Microsoft.DataFactory/factories/pipelines", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_ExecuteETL', variables('INGESTION')))]", + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ingestion_InitScripts", "properties": { - "concurrency": 1, - "activities": [ - { - "name": "Wait", - "description": "Files may not be available immediately after being created.", - "type": "Wait", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "waitTimeInSeconds": 60 + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('clusterName')]" + }, + "databaseName": { + "value": "Ingestion" + }, + "scripts": { + "value": { + "openData": "[variables('$fxv#5')]", + "common": "[variables('$fxv#6')]", + "infra": "[variables('$fxv#7')]", + "rawTables": "[replace(variables('$fxv#8'), '$$rawRetentionInDays$$', string(parameters('rawRetentionInDays')))]" } }, - { - "name": "Set Container Folder Path", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Succeeded" - ] + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" + } + }, + "parameters": { + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer instance." } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "containerFolderPath", - "value": { - "value": "@join(skip(array(split(pipeline().parameters.folderPath, '/')), 1), '/')", - "type": "Expression" + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." } - } - }, - { - "name": "Get Existing Parquet Files", - "description": "Get the previously ingested files so we can get file paths.", - "type": "GetMetadata", - "dependsOn": [ - { - "activity": "Set Container Folder Path", - "dependencyConditions": [ - "Succeeded" - ] + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." } - ], - "policy": { - "timeout": "0.12:00:00", - "retry": 0, - "retryIntervalInSeconds": 30, - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "dataset": { - "referenceName": "ingestion_files", - "type": "DatasetReference", - "parameters": { - "folderPath": "@variables('containerFolderPath')" - } - }, - "fieldList": [ - "childItems" - ], - "storeSettings": { - "type": "AzureBlobFSReadSettings", - "enablePartitionDiscovery": false - }, - "formatSettings": { - "type": "ParquetReadSettings" + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, - { - "name": "Filter Out Folders", - "description": "Remove any folders or manifest files.", - "type": "Filter", - "dependsOn": [ - { - "activity": "Get Existing Parquet Files", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "items": { - "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", - "type": "Expression" + "resources": [ + { + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "condition": { - "value": "@and(equals(item().type, 'File'), not(contains(toLower(item().name), 'manifest.json')))", - "type": "Expression" + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]", + "[resourceId('Microsoft.Resources/deployments', 'ingestion_OpenDataInternalScripts')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ingestion_VersionedScripts", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('clusterName')]" }, - { - "name": "Set Ingestion Timestamp", - "type": "SetVariable", - "dependsOn": [ - { - "activity": "Wait", - "dependencyConditions": [ - "Succeeded" - ] + "databaseName": { + "value": "Ingestion" + }, + "scripts": { + "value": { + "v1_0": "[variables('$fxv#9')]", + "v1_2": "[variables('$fxv#10')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" + } + }, + "parameters": { + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer instance." } - ], - "policy": { - "secureOutput": false, - "secureInput": false }, - "userProperties": [], - "typeProperties": { - "variableName": "timestamp", - "value": { - "value": "@utcNow()", - "type": "Expression" + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." } - } - }, - { - "name": "For Each Old File", - "description": "Loop thru each of the existing files.", - "type": "ForEach", - "dependsOn": [ - { - "activity": "Filter Out Folders", - "dependencyConditions": [ - "Succeeded" - ] - }, - { - "activity": "Set Ingestion Timestamp", - "dependencyConditions": [ - "Succeeded" - ] + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } - ], - "userProperties": [], - "typeProperties": { - "batchCount": "[variables('dataExplorerIngestionCapacity')]", - "items": { - "value": "@activity('Filter Out Folders').output.Value", - "type": "Expression" - }, - "activities": [ - { - "name": "Execute", - "description": "Run the ADX ETL pipeline.", - "type": "ExecutePipeline", - "dependsOn": [], - "policy": { - "secureInput": false - }, - "userProperties": [], - "typeProperties": { - "pipeline": { - "referenceName": "[format('{0}_ETL_dataExplorer', variables('INGESTION'))]", - "type": "PipelineReference" - }, - "waitOnCompletion": true, - "parameters": { - "folderPath": { - "value": "@variables('containerFolderPath')", - "type": "Expression" - }, - "fileName": { - "value": "@item().name", - "type": "Expression" - }, - "originalFileName": { - "value": "[format('@last(array(split(item().name, ''{0}'')))', variables('INGESTION_ID_SEPARATOR'))]", - "type": "Expression" - }, - "ingestionId": { - "value": "[format('@concat(first(array(split(item().name, ''{0}''))), ''_'', variables(''timestamp''))', variables('INGESTION_ID_SEPARATOR'))]", - "type": "Expression" - }, - "table": { - "value": "@concat(first(array(split(variables('containerFolderPath'), '/'))), '_raw')", - "type": "Expression" - } - } - } - } - ] } }, - { - "name": "If No Files", - "description": "If there are no files found, fail the pipeline.", - "type": "IfCondition", - "dependsOn": [ - { - "activity": "Filter Out Folders", - "dependencyConditions": [ - "Succeeded" - ] - } - ], - "userProperties": [], - "typeProperties": { - "expression": { - "value": "@equals(length(activity('Filter Out Folders').output.Value), 0)", - "type": "Expression" + "resources": [ + { + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "ifTrueActivities": [ - { - "name": "Files Not Found", - "type": "Fail", - "dependsOn": [], - "userProperties": [], - "typeProperties": { - "message": { - "value": "@concat('Unable to locate parquet files to ingest from the ', pipeline().parameters.folderPath, ' path. Please confirm the folder path is the full path, including the \"ingestion\" container and not starting with or ending with a slash (\"/\").')", - "type": "Expression" - }, - "errorCode": "IngestionFilesNotFound" - } - } - ] + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" + } } - } - ], - "parameters": { - "folderPath": { - "type": "string" - } - }, - "variables": { - "containerFolderPath": { - "type": "string" - }, - "timestamp": { - "type": "string" - } - }, - "annotations": [ - "New ingestion" - ] + ] + } }, "dependsOn": [ - "appRegistration", - "pipeline_ToDataExplorer" - ], - "metadata": { - "description": "Queues the ingestion_ETL_dataExplorer pipeline to account for Data Factory pipeline trigger limits." - } + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Ingestion')]", + "[resourceId('Microsoft.Resources/deployments', 'ingestion_InitScripts')]" + ] }, - "appRegistration": { + { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_Register", + "apiVersion": "2022-09-01", + "name": "hub_InitScripts", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "app": { - "value": "[parameters('app')]" + "clusterName": { + "value": "[parameters('clusterName')]" }, - "version": { - "value": "[variables('finOpsToolkitVersion')]" + "databaseName": { + "value": "Hub" }, - "features": { - "value": [ - "DataFactory", - "Storage" - ] + "scripts": { + "value": { + "common": "[variables('$fxv#11')]", + "openData": "[variables('$fxv#12')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5436870138046688593" + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" } }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, + "parameters": { + "clusterName": { + "type": "string", "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the FinOps hub Data Explorer instance." } }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." } }, - "_1.IdNameObject": { + "scripts": { "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + } + } + }, + "resources": [ + { + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" + } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", + "[resourceId('Microsoft.Resources/deployments', 'ingestion_InitScripts')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "hub_VersionedScripts", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('clusterName')]" + }, + "databaseName": { + "value": "Hub" + }, + "scripts": { + "value": { + "v1_0": "[variables('$fxv#13')]", + "v1_2": "[variables('$fxv#14')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" + } + }, + "parameters": { + "clusterName": { + "type": "string", "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the FinOps hub Data Explorer instance." } }, - "HubAppFeature": { + "databaseName": { "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." } }, - "HubAppProperties": { + "scripts": { "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + "forceUpdateTag": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } } }, - "functions": [ + "resources": [ { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" + }, + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } } - ], + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", + "[resourceId('Microsoft.Resources/deployments', 'hub_InitScripts')]", + "[resourceId('Microsoft.Resources/deployments', 'ingestion_VersionedScripts')]" + ] + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "hub_LatestScripts", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "clusterName": { + "value": "[parameters('clusterName')]" + }, + "databaseName": { + "value": "Hub" + }, + "scripts": { + "value": { + "latest": "[variables('$fxv#15')]" + } + }, + "continueOnErrors": { + "value": "[parameters('continueOnErrors')]" + }, + "forceUpdateTag": { + "value": "[parameters('forceUpdateTag')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9449675944348204569" + } + }, "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", + "clusterName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer instance." + } + }, + "databaseName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + "scripts": { + "type": "object", + "metadata": { + "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + "continueOnErrors": { + "type": "bool", + "defaultValue": false, "metadata": { - "description": "Required. FinOps hub app getting deployed." + "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." } }, - "version": { + "forceUpdateTag": { "type": "string", + "defaultValue": "[utcNow()]", "metadata": { - "description": "Required. Version number of the FinOps hub app." + "description": "Optional. Forces the table to be updated if different from the last time it was deployed." } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" + } + }, + "resources": [ + { + "copy": { + "name": "cluster::database::script", + "count": "[length(items(parameters('scripts')))]" }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." + "type": "Microsoft.Kusto/clusters/databases/scripts", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", + "properties": { + "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", + "continueOnErrors": "[parameters('continueOnErrors')]", + "forceUpdateTag": "[parameters('forceUpdateTag')]" } + } + ] + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]", + "[resourceId('Microsoft.Kusto/clusters/databases', parameters('clusterName'), 'Hub')]", + "[resourceId('Microsoft.Resources/deployments', 'hub_VersionedScripts')]" + ] + } + ], + "outputs": { + "clusterId": { + "type": "string", + "metadata": { + "description": "The resource ID of the cluster." + }, + "value": "[resourceId('Microsoft.Kusto/clusters', parameters('clusterName'))]" + }, + "principalId": { + "type": "string", + "metadata": { + "description": "The ID of the cluster system assigned managed identity." + }, + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15', 'full').identity.principalId]" + }, + "clusterName": { + "type": "string", + "metadata": { + "description": "The name of the cluster." + }, + "value": "[parameters('clusterName')]" + }, + "clusterUri": { + "type": "string", + "metadata": { + "description": "The URI of the cluster." + }, + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('clusterName')), '2023-08-15').uri]" + }, + "ingestionDbName": { + "type": "string", + "metadata": { + "description": "The name of the database for data ingestion." + }, + "value": "Ingestion" + }, + "hubDbName": { + "type": "string", + "metadata": { + "description": "The name of the database for queries." + }, + "value": "Hub" + }, + "clusterIngestionCapacity": { + "type": "int", + "metadata": { + "description": "Max ingestion capacity of the cluster." + }, + "value": "[coalesce(tryGet(variables('ingestionCapacity'), parameters('clusterSku')), 1)]" + } + } + } + }, + "dependsOn": [ + "core", + "infrastructure" + ] + }, + "dataFactoryResources": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "dataFactoryResources", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[__bicep.newApp(variables('hub'), 'Microsoft FinOps hubs', 'Microsoft.FinOpsHubs', 'DataFactory', 'FinOps hub engine', variables('$fxv#1'))]" + }, + "hubName": { + "value": "[parameters('hubName')]" + }, + "dataFactoryName": { + "value": "[reference('core').outputs.dataFactoryName.value]" + }, + "location": { + "value": "[parameters('location')]" + }, + "tags": { + "value": "[reference('core').outputs.publisherTags.value]" + }, + "tagsByResource": { + "value": "[parameters('tagsByResource')]" + }, + "storageAccountName": { + "value": "[reference('core').outputs.storageAccountName.value]" + }, + "exportContainerName": { + "value": "[reference('cmExports').outputs.exportContainer.value]" + }, + "configContainerName": { + "value": "[reference('core').outputs.configContainer.value]" + }, + "ingestionContainerName": { + "value": "[reference('core').outputs.ingestionContainer.value]" + }, + "dataExplorerName": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterName.value))]", + "dataExplorerPrincipalId": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.principalId.value))]", + "dataExplorerIngestionDatabase": "[if(variables('useFabric'), createObject('value', 'Ingestion'), if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.ingestionDbName.value)))]", + "dataExplorerIngestionCapacity": "[if(variables('useFabric'), createObject('value', parameters('fabricCapacityUnits')), if(not(variables('deployDataExplorer')), createObject('value', 1), createObject('value', reference('dataExplorer').outputs.clusterIngestionCapacity.value)))]", + "dataExplorerUri": "[if(variables('useFabric'), createObject('value', parameters('fabricQueryUri')), if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterUri.value)))]", + "dataExplorerId": "[if(not(variables('deployDataExplorer')), createObject('value', ''), createObject('value', reference('dataExplorer').outputs.clusterId.value))]", + "enableManagedExports": { + "value": "[parameters('enableManagedExports')]" + }, + "enablePublicAccess": { + "value": "[parameters('enablePublicAccess')]" + }, + "keyVaultName": "[if(empty(parameters('remoteHubStorageKey')), createObject('value', ''), createObject('value', reference('remoteHub').outputs.keyVaultName.value))]", + "remoteHubStorageUri": { + "value": "[parameters('remoteHubStorageUri')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "11163540491967572356" + } + }, + "definitions": { + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "storageRoles": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." - } + "keyVaultSku": { + "type": "string" }, - "telemetryString": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." - } + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0}', parameters('app').id)]", - "version": "[parameters('version')]" - } - }, - "resources": [] - } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", - "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" - }, - "resources": { - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", - "properties": { - "name": "[parameters('app').storage]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "storageAccount" - ] + "displayName": { + "type": "string" }, - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", - "properties": { - "name": "[parameters('app').keyVault]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "keyVault" - ] + "suffix": { + "type": "string" }, - "dataFactory::managedVirtualNetwork": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "properties": {}, - "dependsOn": [ - "dataFactory" - ] + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "functions": [ + { + "namespace": "__bicep", + "members": { + "getExportBody": { + "parameters": [ + { + "type": "string", + "name": "exportContainerName" }, - "dataFactory::managedIntegrationRuntime": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", - "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "default", - "type": "ManagedVirtualNetworkReference" - }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('app').hub.location]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 - } - } - } - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedVirtualNetwork" - ] + { + "type": "string", + "name": "datasetType" }, - "dataFactory::linkedService_keyVault": { - "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "keyVault" - ] + { + "type": "string", + "name": "schemaVersion" }, - "dataFactory::linkedService_storageAccount": { - "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" - }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "storageAccount" - ] + { + "type": "bool", + "name": "isMonthly" }, - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] + { + "type": "string", + "name": "exportFormat" }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] + { + "type": "string", + "name": "compressionMode" }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] + { + "type": "string", + "name": "partitionData" }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] + { + "type": "string", + "name": "dataOverwriteBehavior" + } + ], + "output": { + "type": "string", + "value": "[format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}\", \"name\": \"@{{variables(''exportName'')}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'))]" + } + }, + "getExportBodyV2": { + "parameters": [ + { + "type": "string", + "name": "exportContainerName" }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" - }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] + { + "type": "string", + "name": "datasetType" }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" - } - } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] + { + "type": "string", + "name": "schemaVersion" }, - "appTelemetry": { - "condition": "[parameters('app').hub.options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", - "properties": "[variables('telemetryProps')]" + { + "type": "bool", + "name": "isMonthly" }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" - }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" - } - } + { + "type": "string", + "name": "exportFormat" }, - "storageRoleAssignments": { - "copy": { - "name": "storageRoleAssignments", - "count": "[length(variables('factoryStorageRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "storageAccount" - ] + { + "type": "string", + "name": "compressionMode" }, - "triggerManagerIdentity": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "dependsOn": [ - "dataFactory" - ] + { + "type": "string", + "name": "partitionData" }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "triggerManagerIdentity" - ] + { + "type": "string", + "name": "dataOverwriteBehavior" }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "location": "[parameters('app').hub.location]", - "sku": { - "name": "[parameters('app').hub.options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" + { + "type": "string", + "name": "recommendationScope" }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + { + "type": "string", + "name": "recommendationLookbackPeriod" }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "blob" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "string", + "value": "[if(equals(toLower(parameters('datasetType')), 'focuscost'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{10}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), if(equals(toLower(parameters('datasetType')), 'reservationdetails'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}, \"granularity\": \"Daily\" }}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(or(equals(toLower(parameters('datasetType')), 'pricesheet'), equals(toLower(parameters('datasetType')), 'reservationtransactions')), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [] }}}}, \"timeframe\": \"{1}\", \"type\": \"{2}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{3}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{4}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{5}\", \"partitionData\": \"{6}\", \"dataOverwriteBehavior\": \"{7}\", \"compressionMode\": \"{8}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{9}-{10}''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{11}-{12}''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), if(parameters('isMonthly'), 'TheCurrentMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType')), if(parameters('isMonthly'), 'monthly', 'daily'), toLower(parameters('datasetType'))), if(equals(toLower(parameters('datasetType')), 'reservationrecommendations'), format('{{ \"properties\": {{ \"definition\": {{ \"dataSet\": {{ \"configuration\": {{ \"dataVersion\": \"{0}\", \"filters\": [ {{ \"name\": \"reservationScope\", \"value\": \"{1}\" }}, {{ \"name\": \"resourceType\", \"value\": \"{2}\" }}, {{ \"name\": \"lookBackPeriod\", \"value\": \"{3}\" }}] }}}}, \"timeframe\": \"{4}\", \"type\": \"{5}\" }}, \"deliveryInfo\": {{ \"destination\": {{ \"container\": \"{6}\", \"rootFolderPath\": \"@{{if(startswith(item().scope, ''/''), substring(item().scope, 1, sub(length(item().scope), 1)) ,item().scope)}}\", \"type\": \"AzureBlob\", \"resourceId\": \"@{{variables(''storageAccountId'')}}\" }} }}, \"schedule\": {{ \"recurrence\": \"{7}\", \"recurrencePeriod\": {{ \"from\": \"2024-01-01T00:00:00.000Z\", \"to\": \"2050-02-01T00:00:00.000Z\" }}, \"status\": \"Inactive\" }}, \"format\": \"{8}\", \"partitionData\": \"{9}\", \"dataOverwriteBehavior\": \"{10}\", \"compressionMode\": \"{11}\" }}, \"id\": \"@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-{12}-costdetails''))}}\", \"name\": \"@{{toLower(concat(variables(''finOpsHub''), ''-{13}-costdetails''))}}\", \"type\": \"Microsoft.CostManagement/reports\", \"identity\": {{ \"type\": \"systemAssigned\" }}, \"location\": \"global\" }}', parameters('schemaVersion'), parameters('recommendationScope'), parameters('resourceType'), parameters('recommendationLookbackPeriod'), if(parameters('isMonthly'), 'TheLastMonth', 'MonthToDate'), parameters('datasetType'), parameters('exportContainerName'), if(parameters('isMonthly'), 'Monthly', 'Daily'), parameters('exportFormat'), parameters('partitionData'), parameters('dataOverwriteBehavior'), parameters('compressionMode'), if(parameters('isMonthly'), 'monthly', 'daily'), if(parameters('isMonthly'), 'monthly', 'daily')), 'undefined'))))]" + } + } + } + } + ], + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. Temporary app placeholder for the deployments module." + } + }, + "hubName": { + "type": "string", + "metadata": { + "description": "Required. Name of the FinOps hub instance." + } + }, + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory instance." + } + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Required. The name of the Azure Key Vault instance." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. The name of the Azure storage account instance." + } + }, + "exportContainerName": { + "type": "string", + "metadata": { + "description": "Required. The name of the container where Cost Management data is exported." + } + }, + "ingestionContainerName": { + "type": "string", + "metadata": { + "description": "Required. The name of the container where normalized data is ingested." + } + }, + "configContainerName": { + "type": "string", + "metadata": { + "description": "Required. The name of the container where normalized data is ingested." + } + }, + "dataExplorerName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Name of the Azure Data Explorer cluster to use for advanced analytics, if applicable." + } + }, + "dataExplorerId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Resource ID of the Azure Data Explorer cluster to use for advanced analytics, if applicable." + } + }, + "dataExplorerPrincipalId": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. ID of the Azure Data Explorer cluster system assigned managed identity, if applicable." + } + }, + "dataExplorerUri": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. URI of the Azure Data Explorer cluster or Microsoft Fabric eventhouse query endpoint to use for advanced analytics, if applicable." + } + }, + "dataExplorerIngestionDatabase": { + "type": "string", + "defaultValue": "Ingestion", + "metadata": { + "description": "Optional. Name of the Azure Data Explorer ingestion database. Default: \"ingestion\"." + } + }, + "dataExplorerIngestionCapacity": { + "type": "int", + "defaultValue": 1, + "metadata": { + "description": "Optional. Azure Data Explorer ingestion capacity or Microsoft Fabric capacity units. Increase for non-dev/trial SKUs. Default: 1" + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." + } + }, + "remoteHubStorageUri": { + "type": "string", + "metadata": { + "description": "Optional. Remote storage account for ingestion dataset." + } + }, + "tags": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to all resources." + } + }, + "tagsByResource": { + "type": "object", + "defaultValue": {}, + "metadata": { + "description": "Optional. Tags to apply to resources based on their resource type. Resource type specific tags will be merged with tags for all resources." + } + }, + "enableManagedExports": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable managed exports where your FinOps hub instance will create and run Cost Management exports on your behalf. Not supported for Microsoft Customer Agreement (MCA) billing profiles. Requires the ability to grant User Access Administrator role to FinOps hubs, which is required to create Cost Management exports. Default: true." + } + }, + "enablePublicAccess": { + "type": "bool", + "metadata": { + "description": "Required. Enable public access." + } + } + }, + "variables": { + "$fxv#0": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\n#\r\n$adfParams = @{\r\n ResourceGroupName = $env:DataFactoryResourceGroup\r\n DataFactoryName = $env:DataFactoryName\r\n}\r\n\r\n# Delete old triggers\r\n$triggers = Get-AzDataFactoryV2Trigger @adfParams -ErrorAction SilentlyContinue `\r\n| Where-Object { $_.Name -match '^msexports(_(setup|daily|monthly|extract|FileAdded))?$' }\r\n$DeploymentScriptOutputs[\"stopTriggers\"] = $triggers | Stop-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\r\n$DeploymentScriptOutputs[\"deleteTriggers\"] = $triggers | Remove-AzDataFactoryV2Trigger -Force -ErrorAction SilentlyContinue\r\n\r\n# Delete old pipelines\r\n$DeploymentScriptOutputs[\"pipelines\"] = Get-AzDataFactoryV2Pipeline @adfParams -ErrorAction SilentlyContinue `\r\n| Where-Object { $_.Name -match '^(msexports_(backfill|extract|fill|get|run|setup|transform)|config_(BackfillData|ExportData|RunBackfill|RunExports))$' } `\r\n| Remove-AzDataFactoryV2Pipeline -Force -ErrorAction SilentlyContinue\r\n", + "$fxv#1": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nParam(\r\n [switch] $Stop\r\n)\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\nif (-not $Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\n# Loop thru triggers\r\n$env:Triggers.Split('|') `\r\n| ForEach-Object {\r\n $trigger = $_\r\n if ($Stop)\r\n {\r\n Write-Output \"Stopping trigger $trigger...\"\r\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force `\r\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\r\n }\r\n else\r\n {\r\n Write-Output \"Starting trigger $trigger...\"\r\n $triggerOutput = Start-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force\r\n }\r\n if ($triggerOutput)\r\n {\r\n Write-Output \"done...\"\r\n }\r\n else\r\n {\r\n Write-Output \"failed...\"\r\n }\r\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\r\n}\r\n\r\nif ($Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\r\n{\r\n $env:Pipelines.Split('|') `\r\n | ForEach-Object {\r\n Write-Output \"Running the init pipeline...\"\r\n Invoke-AzDataFactoryV2Pipeline `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -PipelineName $_\r\n }\r\n}\r\n", + "$fxv#2": "# Copyright (c) Microsoft Corporation.\r\n# Licensed under the MIT License.\r\n\r\nParam(\r\n [switch] $Stop\r\n)\r\n\r\n# Init outputs\r\n$DeploymentScriptOutputs = @{}\r\n\r\nif (-not $Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\n# Loop thru triggers\r\n$env:Triggers.Split('|') `\r\n| ForEach-Object {\r\n $trigger = $_\r\n if ($Stop)\r\n {\r\n Write-Output \"Stopping trigger $trigger...\"\r\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force `\r\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\r\n }\r\n else\r\n {\r\n Write-Output \"Starting trigger $trigger...\"\r\n $triggerOutput = Start-AzDataFactoryV2Trigger `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -Name $trigger `\r\n -Force\r\n }\r\n if ($triggerOutput)\r\n {\r\n Write-Output \"done...\"\r\n }\r\n else\r\n {\r\n Write-Output \"failed...\"\r\n }\r\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\r\n}\r\n\r\nif ($Stop)\r\n{\r\n Start-Sleep -Seconds 10\r\n}\r\n\r\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\r\n{\r\n $env:Pipelines.Split('|') `\r\n | ForEach-Object {\r\n Write-Output \"Running the init pipeline...\"\r\n Invoke-AzDataFactoryV2Pipeline `\r\n -ResourceGroupName $env:DataFactoryResourceGroup `\r\n -DataFactoryName $env:DataFactoryName `\r\n -PipelineName $_\r\n }\r\n}\r\n", + "focusSchemaVersion": "1.0", + "exportSchemaVersion": "2023-05-01", + "reservationDetailsSchemaVersion": "2023-03-01", + "ftkVersion": "12.0", + "ftkReleaseUri": "[if(endsWith(variables('ftkVersion'), '-dev'), 'https://github.com/microsoft/finops-toolkit/releases/latest/download', format('https://github.com/microsoft/finops-toolkit/releases/download/v{0}', variables('ftkVersion')))]", + "exportApiVersion": "2023-07-01-preview", + "hubDataExplorerName": "hubDataExplorer", + "deployDataExplorer": "[not(empty(parameters('dataExplorerId')))]", + "useFabric": "[and(not(variables('deployDataExplorer')), not(empty(parameters('dataExplorerUri'))))]", + "datasetPropsDefault": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().fileName}", + "type": "Expression" + }, + "folderPath": { + "value": "@{dataset().folderPath}", + "type": "Expression" + } + } + }, + "safeExportContainerName": "[replace(format('{0}', parameters('exportContainerName')), '-', '_')]", + "safeIngestionContainerName": "[replace(format('{0}', parameters('ingestionContainerName')), '-', '_')]", + "safeConfigContainerName": "[replace(format('{0}', parameters('configContainerName')), '-', '_')]", + "managedVnetName": "default", + "ingestionIdFileNameSeparator": "__", + "exportManifestAddedTriggerName": "[format('{0}_ManifestAdded', variables('safeExportContainerName'))]", + "ingestionManifestAddedTriggerName": "[format('{0}_ManifestAdded', variables('safeIngestionContainerName'))]", + "updateConfigTriggerName": "[format('{0}_SettingsUpdated', variables('safeConfigContainerName'))]", + "dailyTriggerName": "[format('{0}_DailySchedule', variables('safeConfigContainerName'))]", + "monthlyTriggerName": "[format('{0}_MonthlySchedule', variables('safeConfigContainerName'))]", + "allHubTriggers": [ + "[variables('exportManifestAddedTriggerName')]", + "[variables('ingestionManifestAddedTriggerName')]", + "[variables('updateConfigTriggerName')]", + "[variables('dailyTriggerName')]", + "[variables('monthlyTriggerName')]" + ], + "autoStartRbacRoles": [ + "673868aa-7521-48a0-acc6-0f60742d39f5" + ], + "storageRbacRoles": "[union(createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'), if(not(parameters('enableManagedExports')), createArray(), createArray('18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')))]", + "adxRbacRoles": [ + "b24988ac-6180-42a0-ab88-20f7382dd24c" + ] + }, + "resources": { + "dataFactory": { + "existing": true, + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[parameters('dataFactoryName')]" + }, + "storageAccount": { + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[parameters('storageAccountName')]" + }, + "keyVault": { + "condition": "[not(empty(parameters('remoteHubStorageUri')))]", + "existing": true, + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[parameters('keyVaultName')]" + }, + "dataExplorerCluster": { + "condition": "[variables('deployDataExplorer')]", + "existing": true, + "type": "Microsoft.Kusto/clusters", + "apiVersion": "2023-08-15", + "name": "[parameters('dataExplorerName')]" + }, + "managedVirtualNetwork": { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('managedVnetName'))]", + "properties": {} + }, + "managedIntegrationRuntime": { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.DataFactory/factories/integrationRuntimes", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ManagedIntegrationRuntime')]", + "properties": { + "type": "Managed", + "managedVirtualNetwork": { + "referenceName": "[variables('managedVnetName')]", + "type": "ManagedVirtualNetworkReference" + }, + "typeProperties": { + "computeProperties": { + "location": "[parameters('location')]", + "dataFlowProperties": { + "computeType": "General", + "coreCount": 8, + "timeToLive": 10, + "cleanup": false, + "customProperties": [] + }, + "copyComputeScaleProperties": { + "dataIntegrationUnit": 16, + "timeToLive": 30 + }, + "pipelineExternalComputeScaleProperties": { + "timeToLive": 30, + "numberOfPipelineNodes": 1, + "numberOfExternalNodes": 1 + } + } + } + }, + "dependsOn": [ + "managedVirtualNetwork" + ] + }, + "storageManagedPrivateEndpoint": { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), parameters('storageAccountName'))]", + "properties": { + "name": "[parameters('storageAccountName')]", + "groupId": "dfs", + "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "fqdns": [ + "[reference('storageAccount').primaryEndpoints.dfs]" + ] + }, + "dependsOn": [ + "managedVirtualNetwork", + "storageAccount" + ] + }, + "keyVaultManagedPrivateEndpoint": { + "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), parameters('keyVaultName'))]", + "properties": { + "name": "[parameters('keyVaultName')]", + "groupId": "vault", + "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]", + "fqdns": [ + "[reference('keyVault').vaultUri]" + ] + }, + "dependsOn": [ + "keyVault", + "managedVirtualNetwork" + ] + }, + "dataExplorerManagedPrivateEndpoint": { + "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", + "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}/{2}', parameters('dataFactoryName'), variables('managedVnetName'), variables('hubDataExplorerName'))]", + "properties": { + "name": "[variables('hubDataExplorerName')]", + "groupId": "cluster", + "privateLinkResourceId": "[parameters('dataExplorerId')]", + "fqdns": [ + "[parameters('dataExplorerUri')]" + ] + }, + "dependsOn": [ + "managedVirtualNetwork" + ] + }, + "triggerManagerIdentity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[format('{0}_triggerManager', parameters('dataFactoryName'))]", + "location": "[parameters('location')]", + "tags": "[union(parameters('tags'), coalesce(tryGet(parameters('tagsByResource'), 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]" + }, + "triggerManagerRoleAssignments": { + "copy": { + "name": "triggerManagerRoleAssignments", + "count": "[length(variables('autoStartRbacRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('dataFactoryName'))]", + "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('dataFactoryName'))))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", + "principalId": "[reference('triggerManagerIdentity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "triggerManagerIdentity" + ] + }, + "factoryIdentityStorageRoleAssignments": { + "copy": { + "name": "factoryIdentityStorageRoleAssignments", + "count": "[length(variables('storageRbacRoles'))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName'))]", + "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), variables('storageRbacRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('storageRbacRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory" + ] + }, + "factoryIdentityDataExplorerRoleAssignments": { + "copy": { + "name": "factoryIdentityDataExplorerRoleAssignments", + "count": "[length(variables('adxRbacRoles'))]" + }, + "condition": "[variables('deployDataExplorer')]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Kusto/clusters/{0}', parameters('dataExplorerName'))]", + "name": "[guid(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), variables('adxRbacRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('adxRbacRoles')[copyIndex()])]", + "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "dataFactory" + ] + }, + "linkedService_keyVault": { + "condition": "[not(empty(parameters('remoteHubStorageUri')))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('keyVaultName'))]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureKeyVault", + "typeProperties": { + "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('keyVaultName')), '2023-02-01').vaultUri]" + }, + "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" + }, + "dependsOn": [ + "managedIntegrationRuntime" + ] + }, + "linkedService_storageAccount": { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('storageAccountName'))]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('storageAccountName')), '2021-08-01').primaryEndpoints.dfs]" + }, + "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" + }, + "dependsOn": [ + "managedIntegrationRuntime" + ] + }, + "linkedService_dataExplorer": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('hubDataExplorerName'))]", + "properties": { + "type": "AzureDataExplorer", + "parameters": { + "database": { + "type": "String", + "defaultValue": "[parameters('dataExplorerIngestionDatabase')]" + } + }, + "typeProperties": { + "endpoint": "[parameters('dataExplorerUri')]", + "database": "@{linkedService().database}", + "tenant": "[reference('dataFactory', '2018-06-01', 'full').identity.tenantId]", + "servicePrincipalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + }, + "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" + }, + "dependsOn": [ + "dataFactory", + "managedIntegrationRuntime" + ] + }, + "linkedService_remoteHubStorage": { + "condition": "[not(empty(parameters('remoteHubStorageUri')))]", + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'remoteHubStorage')]", + "properties": { + "annotations": [], + "parameters": {}, + "type": "AzureBlobFS", + "typeProperties": { + "url": "[parameters('remoteHubStorageUri')]", + "accountKey": { + "type": "AzureKeyVaultSecret", + "store": { + "referenceName": "[parameters('keyVaultName')]", + "type": "LinkedServiceReference" + }, + "secretName": "[format('{0}-storage-key', toLower(parameters('hubName')))]" + } + }, + "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" + }, + "dependsOn": [ + "linkedService_keyVault", + "managedIntegrationRuntime" + ] + }, + "linkedService_ftkRepo": { + "type": "Microsoft.DataFactory/factories/linkedservices", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ftkRepo')]", + "properties": { + "parameters": { + "filePath": { + "type": "string" + } + }, + "annotations": [], + "type": "HttpServer", + "typeProperties": { + "url": "@concat('https://github.com/microsoft/finops-toolkit/', linkedService().filePath)", + "enableServerCertificateValidation": true, + "authenticationType": "Anonymous" + }, + "connectVia": "[if(parameters('enablePublicAccess'), null(), createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'))]" + }, + "dependsOn": [ + "managedIntegrationRuntime" + ] + }, + "dataset_config": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeConfigContainerName'))]", + "properties": { + "annotations": [], + "parameters": { + "fileName": { + "type": "String", + "defaultValue": "settings.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[parameters('configContainerName')]" + } + }, + "type": "Json", + "typeProperties": "[variables('datasetPropsDefault')]", + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('storageAccountName')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_storageAccount" + ] + }, + "dataset_manifest": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'manifest')]", + "properties": { + "annotations": [], + "parameters": { + "fileName": { + "type": "String", + "defaultValue": "manifest.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[parameters('exportContainerName')]" + } + }, + "type": "Json", + "typeProperties": "[variables('datasetPropsDefault')]", + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('storageAccountName')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_storageAccount" + ] + }, + "dataset_msexports": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeExportContainerName'))]", + "properties": { + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + "fileSystem": "[variables('safeExportContainerName')]" + }, + "columnDelimiter": ",", + "escapeChar": "\"", + "quoteChar": "\"", + "firstRowAsHeader": true + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('storageAccountName')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_storageAccount" + ] + }, + "dataset_msexports_gzip": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_gzip', variables('safeExportContainerName')))]", + "properties": { + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" - }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] - }, - "dependsOn": [ - "storageAccount" - ] + "fileSystem": "[variables('safeExportContainerName')]" + }, + "columnDelimiter": ",", + "escapeChar": "\"", + "quoteChar": "\"", + "firstRowAsHeader": true, + "compressionCodec": "Gzip" + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('storageAccountName')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_storageAccount" + ] + }, + "dataset_msexports_parquet": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_parquet', variables('safeExportContainerName')))]", + "properties": { + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", - "properties": { - "sku": { - "name": "[parameters('app').hub.options.keyVaultSku]", - "family": "A" - }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] - } - } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" - } - }, - "dependsOn": [ - "dataFactory" - ] + "fileSystem": "[variables('safeExportContainerName')]" + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[parameters('storageAccountName')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_storageAccount" + ] + }, + "dataset_ingestion": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('safeIngestionContainerName'))]", + "properties": { + "annotations": [], + "parameters": { + "blobPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileName": { + "value": "@{dataset().blobPath}", + "type": "Expression" }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} + "fileSystem": "[variables('safeIngestionContainerName')]" + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[if(empty(parameters('remoteHubStorageUri')), parameters('storageAccountName'), 'remoteHubStorage')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_remoteHubStorage", + "linkedService_storageAccount" + ] + }, + "dataset_ingestion_files": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_files', variables('safeIngestionContainerName')))]", + "properties": { + "annotations": [], + "parameters": { + "folderPath": { + "type": "String" + } + }, + "type": "Parquet", + "typeProperties": { + "location": { + "type": "AzureBlobFSLocation", + "fileSystem": "[variables('safeIngestionContainerName')]", + "folderPath": { + "value": "@dataset().folderPath", + "type": "Expression" + } + } + }, + "linkedServiceName": { + "parameters": {}, + "referenceName": "[if(empty(parameters('remoteHubStorageUri')), parameters('storageAccountName'), 'remoteHubStorage')]", + "type": "LinkedServiceReference" + } + }, + "dependsOn": [ + "linkedService_remoteHubStorage", + "linkedService_storageAccount" + ] + }, + "dataset_dataExplorer": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('hubDataExplorerName'))]", + "properties": { + "type": "AzureDataExplorerTable", + "linkedServiceName": { + "parameters": { + "database": "@dataset().database" + }, + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference" + }, + "parameters": { + "database": { + "type": "String", + "defaultValue": "[parameters('dataExplorerIngestionDatabase')]" + }, + "table": { + "type": "String" + } + }, + "typeProperties": { + "table": { + "value": "@dataset().table", + "type": "Expression" + } + } + }, + "dependsOn": [ + "linkedService_dataExplorer" + ] + }, + "dataset_ftkReleaseFile": { + "type": "Microsoft.DataFactory/factories/datasets", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), 'ftkReleaseFile')]", + "properties": { + "linkedServiceName": { + "referenceName": "ftkRepo", + "type": "LinkedServiceReference" + }, + "parameters": { + "fileName": { + "type": "string" + }, + "version": { + "type": "string", + "defaultValue": "[variables('ftkVersion')]" + } + }, + "annotations": [], + "type": "DelimitedText", + "typeProperties": { + "location": { + "type": "HttpServerLocation", + "relativeUrl": { + "value": "@concat('releases/download/v', dataset().version, '/', dataset().fileName)", + "type": "Expression" + } + }, + "columnDelimiter": ",", + "escapeChar": "\\", + "firstRowAsHeader": true, + "quoteChar": "\"" + }, + "schema": [] + }, + "dependsOn": [ + "linkedService_ftkRepo" + ] + }, + "trigger_DailySchedule": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('dailyTriggerName'))]", + "properties": { + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[format('{0}_StartExportProcess', variables('safeConfigContainerName'))]", + "type": "PipelineReference" }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('app').keyVault)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.keyVault]" - }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] - }, - "dependsOn": [ - "keyVault" - ] + "parameters": { + "Recurrence": "Daily" + } + } + ], + "type": "ScheduleTrigger", + "typeProperties": { + "recurrence": { + "frequency": "Hour", + "interval": 24, + "startTime": "2023-01-01T01:01:00", + "timeZone": "[reference('azuretimezones').outputs.Timezone.value]" + } + } + }, + "dependsOn": [ + "azuretimezones", + "pipeline_StartExportProcess", + "stopTriggers" + ] + }, + "trigger_MonthlySchedule": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), variables('monthlyTriggerName'))]", + "properties": { + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[format('{0}_StartExportProcess', variables('safeConfigContainerName'))]", + "type": "PipelineReference" }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", - "getStoragePrivateEndpointConnections", - "keyVault" + "parameters": { + "Recurrence": "Monthly" + } + } + ], + "type": "ScheduleTrigger", + "typeProperties": { + "recurrence": { + "frequency": "Month", + "interval": 1, + "startTime": "2023-01-05T01:11:00", + "timeZone": "[reference('azuretimezones').outputs.Timezone.value]", + "schedule": { + "monthDays": [ + 2, + 5, + 19 ] + } + } + } + }, + "dependsOn": [ + "azuretimezones", + "pipeline_StartExportProcess", + "stopTriggers" + ] + }, + "pipeline_InitializeHub": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_InitializeHub', variables('safeConfigContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Config", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } + "formatSettings": { + "type": "JsonReadSettings" } }, - "dependsOn": [ - "getKeyVaultPrivateEndpointConnections", - "keyVault" - ] - }, - "getStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference" + } + } + }, + { + "name": "Set Version", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "version", + "value": { + "value": "@activity('Get Config').output.firstRow.version", + "type": "Expression" + } + } + }, + { + "name": "Set Scopes", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "scopes", + "value": { + "value": "@string(activity('Get Config').output.firstRow.scopes)", + "type": "Expression" + } + } + }, + { + "name": "Set Retention", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "retention", + "value": { + "value": "@string(activity('Get Config').output.firstRow.retention)", + "type": "Expression" + } + } + }, + { + "name": "Until Capacity Is Available", + "type": "Until", + "dependsOn": [ + { + "activity": "Set Version", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Scopes", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Retention", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@equals(variables('tryAgain'), false)", + "type": "Expression" + }, + "activities": [ + { + "name": "Confirm Ingestion Capacity", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } + "userProperties": [], + "typeProperties": { + "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", + "commandTimeout": "00:20:00" }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" } } - } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", - "stopTriggers", - "storageAccount" - ] - }, - "approveStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ + { + "name": "If Has Capacity", + "type": "IfCondition", + "dependsOn": [ { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } + "activity": "Confirm Ingestion Capacity", + "dependencyConditions": [ + "Succeeded" + ] } ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "getStoragePrivateEndpointConnections", - "storageAccount" - ] - }, - "stopTriggers": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "arguments": { - "value": "-Stop" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", + "type": "Expression" + }, + "ifFalseActivities": [ { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" + "name": "Wait for Ingestion", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 15 + } }, { - "name": "DataFactoryName", - "value": "[parameters('app').dataFactory]" - } - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" + "name": "Try Again", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait for Ingestion", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "value": { - "type": "string" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": true } } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" + ], + "ifTrueActivities": [ + { + "name": "Set ingestion policy in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "version": { - "type": "string" + "userProperties": [], + "typeProperties": { + "command": { + "value": "[if(variables('useFabric'), format('.show database {0} policy managed_identity', parameters('dataExplorerIngestionDatabase')), format('.alter-merge database {0} policy managed_identity \"[ {{ ''ObjectId'' : ''{1}'', ''AllowedUsages'' : ''NativeIngestion'' }}]\"', parameters('dataExplorerIngestionDatabase'), parameters('dataExplorerPrincipalId')))]", + "type": "Expression" + }, + "commandTimeout": "00:20:00" }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } + } + }, + { + "name": "Save Hub Settings in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Set ingestion policy in ADX", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" + "userProperties": [], + "typeProperties": { + "command": { + "value": "@concat('.append HubSettingsLog <| print version=\"', variables('version'), '\",scopes=dynamic(', variables('scopes'), '),retention=dynamic(', variables('retention'), ') | extend scopes = iff(isnull(scopes[0]), pack_array(scopes), scopes) | mv-apply scopeObj = scopes on (where isnotempty(scopeObj.scope) | summarize scopes = make_set(scopeObj.scope))')", + "type": "Expression" + }, + "commandTimeout": "00:20:00" }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" } } }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + { + "name": "Update PricingUnits in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Save Hub Settings in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace PricingUnits <| externaldata(x_PricingUnitDescription: string, AccountTypes: string, x_PricingBlockSize: decimal, PricingUnit: string)[@\"{0}/PricingUnits.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away AccountTypes', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } + }, + { + "name": "Update Regions in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update PricingUnits in ADX", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace Regions <| externaldata(ResourceLocation: string, RegionId: string, RegionName: string)[@\"{0}/Regions.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" + }, + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" } } }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." + { + "name": "Update ResourceTypes in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update Regions in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace ResourceTypes <| externaldata(x_ResourceType: string, SingularDisplayName: string, PluralDisplayName: string, LowerSingularDisplayName: string, LowerPluralDisplayName: string, IsPreview: bool, Description: string, IconUri: string, Links: string)[@\"{0}/ResourceTypes.csv\"] with (format=\"csv\", ignoreFirstRecord=true) | project-away Links', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" }, - "name": { - "type": "string" + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } } }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" + { + "name": "Update Services in ADX", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Update ResourceTypes in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "keyVault": { - "type": "string" + "userProperties": [], + "typeProperties": { + "command": "[format('.set-or-replace Services <| externaldata(x_ConsumedService: string, x_ResourceType: string, ServiceName: string, ServiceCategory: string, ServiceSubcategory: string, PublisherName: string, x_PublisherCategory: string, x_Environment: string, x_ServiceModel: string)[@\"{0}/Services.csv\"] with (format=\"csv\", ignoreFirstRecord=true)', variables('ftkReleaseUri'))]", + "commandTimeout": "00:20:00" }, - "storage": { - "type": "string" + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } + } + }, + { + "name": "Ingestion Complete", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Update Services in ADX", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false } } + ] + } + }, + { + "name": "Abort On Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "If Has Capacity", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false + } + } + ], + "timeout": "0.02:00:00" + } + }, + { + "name": "Timeout Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Until Capacity Is Available", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": "Data Explorer ingestion timed out after 2 hours while waiting for available capacity. Please re-run this pipeline to re-attempt ingestion. If you continue to see this error, please report an issue at https://aka.ms/ftk/ideas.", + "errorCode": "DataExplorerIngestionTimeout" + } + } + ], + "concurrency": 1, + "variables": { + "version": { + "type": "String" + }, + "scopes": { + "type": "String" + }, + "retention": { + "type": "String" + }, + "tryAgain": { + "type": "Boolean", + "defaultValue": true + } + } + }, + "dependsOn": [ + "dataset_config", + "linkedService_dataExplorer" + ], + "metadata": { + "description": "Initializes the hub instance based on the configuration settings." + } + }, + "pipeline_StartBackfillProcess": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_StartBackfillProcess', variables('safeConfigContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Config", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('fileName')", + "type": "Expression" + }, + "folderPath": { + "value": "@variables('folderPath')", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set backfill end date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "endDate", + "value": { + "value": "@addDays(startOfMonth(utcNow()), -1)", + "type": "Expression" + } + } + }, + { + "name": "Set backfill start date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "startDate", + "value": { + "value": "@subtractFromTime(startOfMonth(utcNow()), activity('Get Config').output.firstRow.retention.ingestion.months, 'Month')", + "type": "Expression" + } + } + }, + { + "name": "Set export start date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set backfill start date", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "thisMonth", + "value": { + "value": "@startOfMonth(variables('endDate'))", + "type": "Expression" + } + } + }, + { + "name": "Set export end date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set export start date", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "nextMonth", + "value": { + "value": "@startOfMonth(subtractFromTime(variables('thisMonth'), 1, 'Month'))", + "type": "Expression" + } + } + }, + { + "name": "Every Month", + "type": "Until", + "dependsOn": [ + { + "activity": "Set export end date", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set backfill end date", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@less(variables('thisMonth'), variables('startDate'))", + "type": "Expression" + }, + "activities": [ + { + "name": "Update export start date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Backfill data", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "thisMonth", + "value": { + "value": "@variables('nextMonth')", + "type": "Expression" } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } + } + }, + { + "name": "Update export end date", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Update export start date", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "variableName": "nextMonth", + "value": { + "value": "@subtractFromTime(variables('thisMonth'), 1, 'Month')", + "type": "Expression" + } + } + }, + { + "name": "Backfill data", + "type": "ExecutePipeline", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_RunBackfillJob', variables('safeConfigContainerName'))]", + "type": "PipelineReference" }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" + "waitOnCompletion": true, + "parameters": { + "StartDate": { + "value": "@variables('thisMonth')", + "type": "Expression" }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "EndDate": { + "value": "@addDays(addToTime(variables('thisMonth'), 1, 'Month'), -1)", + "type": "Expression" } } + } + } + ], + "timeout": "0.02:00:00" + } + } + ], + "concurrency": 1, + "variables": { + "exportName": { + "type": "String" + }, + "storageAccountId": { + "type": "String", + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + }, + "finOpsHub": { + "type": "String", + "defaultValue": "[parameters('hubName')]" + }, + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" + }, + "fileName": { + "type": "String", + "defaultValue": "settings.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[parameters('configContainerName')]" + }, + "endDate": { + "type": "String" + }, + "startDate": { + "type": "String" + }, + "thisMonth": { + "type": "String" + }, + "nextMonth": { + "type": "String" + } + } + }, + "dependsOn": [ + "dataset_config", + "pipeline_RunBackfillJob" + ], + "metadata": { + "description": "Runs the backfill job for each month based on retention settings." + } + }, + "pipeline_RunBackfillJob": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_RunBackfillJob', variables('safeConfigContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Config", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('fileName')", + "type": "Expression" + }, + "folderPath": { + "value": "@variables('folderPath')", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Scopes", + "description": "Save scopes to test if it is an array", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@activity('Get Config').output.firstRow.scopes", + "type": "Expression" + } + } + }, + { + "name": "Set Scopes as Array", + "description": "Wraps a single scope object into an array to work around the PowerShell bug where single-item arrays are sometimes written as a single object instead of an array.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Scopes", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@createArray(activity('Get Config').output.firstRow.scopes)", + "type": "Expression" + } + } + }, + { + "name": "Filter Invalid Scopes", + "description": "Remove any invalid scopes to avoid errors.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Set Scopes", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Scopes as Array", + "dependencyConditions": [ + "Skipped", + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@variables('scopesArray')", + "type": "Expression" + }, + "condition": { + "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", + "type": "Expression" + } + } + }, + { + "name": "ForEach Export Scope", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Filter Invalid Scopes", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Filter Invalid Scopes').output.Value", + "type": "Expression" + }, + "isSequential": true, + "activities": [ + { + "name": "Set backfill export name", + "type": "SetVariable", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "variableName": "exportName", + "value": { + "value": "@toLower(concat(variables('finOpsHub'), '-monthly-costdetails'))", + "type": "Expression" + } + } + }, + { + "name": "Trigger backfill export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "Set backfill export name", + "dependencyConditions": [ + "Completed" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 1, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{variables(''exportName'')}}/run?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] + "method": "POST", + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunBackfill@{0}', variables('ftkVersion'))]", + "Content-Type": "application/json", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "body": "{\"timePeriod\" : { \"from\" : \"@{pipeline().parameters.StartDate}\", \"to\" : \"@{pipeline().parameters.EndDate}\" }}", + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } } } } - }, - "dependsOn": [ - "appTelemetry", - "dataFactory", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" ] } - }, - "outputs": { - "dataFactoryId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Data Factory instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" - }, - "keyVaultId": { - "type": "string", - "metadata": { - "description": "Resource ID of the Key Vault instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" - }, - "storageAccountId": { - "type": "string", - "metadata": { - "description": "Resource ID of the storage account instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" - }, - "triggerManagerIdentityName": { - "type": "string", - "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." - }, - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - } } - } - } - }, - "ingestion_OpenDataInternalScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionOpenDataInternal", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", + ], + "concurrency": 1, "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" - }, - "databaseName": { - "value": "[variables('INGESTION_DB')]" - }, - "scripts": { - "value": { - "OpenDataFunctions_resource_type_1": "[variables('$fxv#0')]", - "OpenDataFunctions_resource_type_2": "[variables('$fxv#1')]", - "OpenDataFunctions_resource_type_3": "[variables('$fxv#2')]", - "OpenDataFunctions_resource_type_4": "[variables('$fxv#3')]", - "OpenDataFunctions_resource_type_5": "[variables('$fxv#4')]" - } - }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "StartDate": { + "type": "string" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "EndDate": { + "type": "string" } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" - } - }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." - } - } + "variables": { + "exportName": { + "type": "String" }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" - } - } - ] - } - }, - "dependsOn": [ - "cluster", - "cluster::ingestionDb" - ] - }, - "ingestion_InitScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionInit", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" + "storageAccountId": { + "type": "String", + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" }, - "databaseName": { - "value": "[variables('INGESTION_DB')]" + "finOpsHub": { + "type": "String", + "defaultValue": "[parameters('hubName')]" }, - "scripts": { - "value": { - "openData": "[variables('$fxv#5')]", - "common": "[variables('$fxv#6')]", - "infra": "[variables('$fxv#7')]", - "rawTables": "[replace(variables('$fxv#8'), '$$rawRetentionInDays$$', string(parameters('rawRetentionInDays')))]" - } + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "fileName": { + "type": "String", + "defaultValue": "settings.json" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "folderPath": { + "type": "String", + "defaultValue": "[parameters('configContainerName')]" + }, + "scopesArray": { + "type": "Array" } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" + } + }, + "dependsOn": [ + "dataset_config" + ], + "metadata": { + "description": "Creates and triggers exports for all defined scopes for the specified date range." + } + }, + "pipeline_StartExportProcess": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_StartExportProcess', variables('safeConfigContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Config", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('fileName')", + "type": "Expression" + }, + "folderPath": { + "value": "@variables('folderPath')", + "type": "Expression" + } + } + } } }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + { + "name": "Set Scopes", + "description": "Save scopes to test if it is an array", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@activity('Get Config').output.firstRow.scopes", + "type": "Expression" } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + { + "name": "Set Scopes as Array", + "description": "Wraps a single scope object into an array to work around the PowerShell bug where single-item arrays are sometimes written as a single object instead of an array.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Scopes", + "dependencyConditions": [ + "Failed" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@createArray(activity('Get Config').output.firstRow.scopes)", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "Filter Invalid Scopes", + "description": "Remove any invalid scopes to avoid errors.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Set Scopes", + "dependencyConditions": [ + "Succeeded" + ] }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "activity": "Set Scopes as Array", + "dependencyConditions": [ + "Succeeded", + "Skipped" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@variables('scopesArray')", + "type": "Expression" + }, + "condition": { + "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", + "type": "Expression" } } - ] - } - }, - "dependsOn": [ - "cluster", - "cluster::ingestionDb", - "ingestion_OpenDataInternalScripts" - ] - }, - "ingestion_VersionedScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.IngestionVersioned", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", + }, + { + "name": "ForEach Export Scope", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Filter Invalid Scopes", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Filter Invalid Scopes').output.Value", + "type": "Expression" + }, + "isSequential": true, + "activities": [ + { + "name": "Get exports for scope", + "type": "WebActivity", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "GET", + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "Run exports for scope", + "type": "ExecutePipeline", + "dependsOn": [ + { + "activity": "Get exports for scope", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_RunExportJobs', variables('safeConfigContainerName'))]", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "ExportScopes": { + "value": "@activity('Get exports for scope').output.value", + "type": "Expression" + }, + "Recurrence": { + "value": "@pipeline().parameters.Recurrence", + "type": "Expression" + } + } + } + } + ] + } + } + ], + "concurrency": 1, "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" + "Recurrence": { + "type": "string", + "defaultValue": "Daily" + } + }, + "variables": { + "fileName": { + "type": "String", + "defaultValue": "settings.json" }, - "databaseName": { - "value": "[variables('INGESTION_DB')]" + "folderPath": { + "type": "String", + "defaultValue": "[parameters('configContainerName')]" }, - "scripts": { - "value": { - "v1_0": "[variables('$fxv#9')]", - "v1_2": "[variables('$fxv#10')]" - } + "finOpsHub": { + "type": "String", + "defaultValue": "[parameters('hubName')]" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "scopesArray": { + "type": "Array" } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" - } - }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." - } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." - } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." - } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." - } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." - } - } - }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" - } - } - ] } }, "dependsOn": [ - "cluster", - "cluster::ingestionDb", - "ingestion_InitScripts" - ] + "dataset_config", + "pipeline_RunExportJobs" + ], + "metadata": { + "description": "Gets a list of all Cost Management exports configured for this hub based on the scopes defined in settings.json, then runs each export using the config_RunExportJobs pipeline." + } }, - "hub_InitScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubInit", + "pipeline_RunExportJobs": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_RunExportJobs', variables('safeConfigContainerName')))]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" - }, - "databaseName": { - "value": "[variables('HUB_DB')]" - }, - "scripts": { - "value": { - "common": "[variables('$fxv#11')]", - "openData": "[variables('$fxv#12')]" + "activities": [ + { + "name": "ForEach export scope", + "type": "ForEach", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@pipeline().parameters.exportScopes", + "type": "Expression" + }, + "isSequential": true, + "activities": [ + { + "name": "If scheduled", + "type": "IfCondition", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@and( startswith(toLower(item().name), toLower(variables('hubName'))), and(contains(string(item().properties.schedule), 'recurrence'), equals(toLower(item().properties.schedule.recurrence), toLower(pipeline().parameters.Recurrence))))", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Trigger export", + "type": "WebActivity", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "method": "POST", + "url": { + "value": "[format('@{{replace(toLower(concat(variables(''resourceManagementUri''),item().id)), ''com//'', ''com/'')}}/run?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "body": " ", + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + } + ] + } + } + ] } + } + ], + "concurrency": 1, + "parameters": { + "ExportScopes": { + "type": "array" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" - }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "Recurrence": { + "type": "string", + "defaultValue": "Daily" } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" - } + "variables": { + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." + "hubName": { + "type": "String", + "defaultValue": "[parameters('hubName')]" + } + } + }, + "dependsOn": [ + "dataset_config" + ], + "metadata": { + "description": "Runs the specified Cost Management exports." + } + }, + "pipeline_ConfigureExports": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ConfigureExports', variables('safeConfigContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Config", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('fileName')", + "type": "Expression" + }, + "folderPath": { + "value": "@variables('folderPath')", + "type": "Expression" + } + } } - }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + } + }, + { + "name": "Save Scopes", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Get Config", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@activity('Get Config').output.firstRow.scopes", + "type": "Expression" } - }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + } + }, + { + "name": "Save Scopes as Array", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Save Scopes", + "dependencyConditions": [ + "Failed" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "userProperties": [], + "typeProperties": { + "variableName": "scopesArray", + "value": { + "value": "@array(activity('Get Config').output.firstRow.scopes)", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "Filter Invalid Scopes", + "type": "Filter", + "dependsOn": [ + { + "activity": "Save Scopes", + "dependencyConditions": [ + "Succeeded" + ] }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "activity": "Save Scopes as Array", + "dependencyConditions": [ + "Skipped", + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@variables('scopesArray')", + "type": "Expression" + }, + "condition": { + "value": "@and(not(empty(item().scope)), not(equals(item().scope, '/')))", + "type": "Expression" + } + } + }, + { + "name": "ForEach Export Scope", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Filter Invalid Scopes", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Filter Invalid Scopes').output.value", + "type": "Expression" + }, + "isSequential": true, + "activities": [ + { + "name": "Set Export Type", + "type": "SetVariable", + "dependsOn": [], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "exportScopeType", + "value": { + "value": "@if(contains(toLower(item().scope), 'providers/microsoft.billing/billingaccounts'), if(contains(toLower(item().scope), ':'), 'mca', 'ea'), if(contains(toLower(item().scope), 'subscriptions/'), 'subscription', 'undefined'))", + "type": "Expression" + } + } + }, + { + "name": "Switch Export Type", + "type": "Switch", + "dependsOn": [ + { + "activity": "Set Export Type", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "on": { + "value": "@toLower(variables('exportScopeType'))", + "type": "Expression" + }, + "cases": [ + { + "value": "ea", + "activities": [ + { + "name": "EA open month focus export", + "type": "WebActivity", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "EA closed month focus export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA open month focus export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "EA monthly pricesheet export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA closed month focus export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'Pricesheet', variables('exportSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "Trigger EA monthly pricesheet export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA monthly pricesheet export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "method": "POST", + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-pricesheet''))}}/run?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.Prices@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "body": " ", + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "EA daily reservation details export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA monthly pricesheet export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationDetails', variables('reservationDetailsSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationDetails@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "EA daily reservation transactions export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA daily reservation details export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-reservationtransactions''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationTransactions', variables('exportSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationTransactions@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "EA daily shared 30day virtualmachines", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "EA daily reservation transactions export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-recommendations-shared-last30days-virtualmachines''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'ReservationRecommendations', variables('exportSchemaVersion'), false(), 'CSV', 'None', 'true', 'CreateNewReport', 'Shared', 'Last30Days', 'VirtualMachines')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.ReservationRecommendations.VM.Shared.30d@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + } + ] + }, + { + "value": "subscription", + "activities": [ + { + "name": "Subscription open month focus export", + "type": "WebActivity", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-daily-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), false(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsDaily@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + }, + { + "name": "Subscription closed month focus export", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "Subscription open month focus export", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.00:05:00", + "retry": 2, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "url": { + "value": "[format('@{{variables(''resourceManagementUri'')}}@{{item().scope}}/providers/Microsoft.CostManagement/exports/@{{toLower(concat(variables(''finOpsHub''), ''-monthly-costdetails''))}}?api-version={0}', variables('exportApiVersion'))]", + "type": "Expression" + }, + "method": "PUT", + "body": { + "value": "[__bicep.getExportBodyV2(parameters('exportContainerName'), 'FocusCost', variables('focusSchemaVersion'), true(), 'Parquet', 'Snappy', 'true', 'CreateNewReport', '', '', '')]", + "type": "Expression" + }, + "headers": { + "x-ms-command-name": "[format('FinOpsToolkit.Hubs.config_RunExportJobs.CostsMonthly@{0}', variables('ftkVersion'))]", + "ClientType": "[format('FinOpsToolkit.Hubs@{0}', variables('ftkVersion'))]" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "@variables('resourceManagementUri')", + "type": "Expression" + } + } + } + } + ] + }, + { + "value": "mca", + "activities": [ + { + "name": "Export Type Unsupported Error", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('MCA agreements are not supported for managed exports :',variables('exportScope'))", + "type": "Expression" + }, + "errorCode": "ExportTypeUnsupported" + } + } + ] + } + ], + "defaultActivities": [ + { + "name": "Export Type Not Defined Error", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to determine the export scope type for :',variables('exportScope'))", + "type": "Expression" + }, + "errorCode": "ExportTypeNotDefined" + } + } + ] + } + } + ] } - ] - } - }, - "dependsOn": [ - "cluster", - "cluster::hubDb", - "ingestion_InitScripts" - ] - }, - "hub_VersionedScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubVersioned", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" + } + ], + "concurrency": 1, + "variables": { + "scopesArray": { + "type": "Array" }, - "databaseName": { - "value": "[variables('HUB_DB')]" + "exportName": { + "type": "String" }, - "scripts": { - "value": { - "v1_0": "[variables('$fxv#13')]", - "v1_2": "[variables('$fxv#14')]" - } + "exportScope": { + "type": "String" }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" + "exportScopeType": { + "type": "String" }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" + "storageAccountId": { + "type": "String", + "defaultValue": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" + }, + "finOpsHub": { + "type": "String", + "defaultValue": "[parameters('hubName')]" + }, + "resourceManagementUri": { + "type": "String", + "defaultValue": "[environment().resourceManager]" + }, + "fileName": { + "type": "String", + "defaultValue": "settings.json" + }, + "folderPath": { + "type": "String", + "defaultValue": "[parameters('configContainerName')]" } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" + } + }, + "dependsOn": [ + "dataset_config" + ], + "metadata": { + "description": "Creates Cost Management exports for supported scopes." + } + }, + "pipeline_ExecuteExportsETL": { + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ExecuteETL', variables('safeExportContainerName')))]", + "properties": { + "activities": [ + { + "name": "Wait", + "description": "Files may not be available immediately after being created.", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 60 } }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." + { + "name": "Read Manifest", + "description": "Load the export manifest to determine the scope, dataset, and date range.", + "type": "Lookup", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Completed" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@pipeline().parameters.fileName", + "type": "Expression" + }, + "folderPath": { + "value": "@pipeline().parameters.folderPath", + "type": "Expression" + } + } } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + { + "name": "Set Has No Rows", + "description": "Check the row count ", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + "userProperties": [], + "typeProperties": { + "variableName": "hasNoRows", + "value": { + "value": "@or(equals(activity('Read Manifest').output.firstRow.blobCount, null), equals(activity('Read Manifest').output.firstRow.blobCount, 0))", + "type": "Expression" + } + } + }, + { + "name": "Set Export Dataset Type", + "description": "Save the dataset type from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + "userProperties": [], + "typeProperties": { + "variableName": "exportDatasetType", + "value": { + "value": "@activity('Read Manifest').output.firstRow.exportConfig.type", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" - }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "name": "Set MCA Column", + "description": "Determines if the dataset schema has channel-specific columns and saves the column name that only exists in MCA to determine if it is an MCA dataset.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "mcaColumnToCheck", + "value": { + "value": "@if(contains(createArray('pricesheet', 'reservationtransactions'), toLower(variables('exportDatasetType'))), 'BillingProfileId', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Net Savings', null))", + "type": "Expression" } } - ] - } - }, - "dependsOn": [ - "cluster", - "cluster::hubDb", - "hub_InitScripts", - "ingestion_VersionedScripts" - ] - }, - "hub_LatestScripts": { - "condition": "[variables('useAzure')]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_ADX.HubLatest", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "clusterName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" - }, - "databaseName": { - "value": "[variables('HUB_DB')]" }, - "scripts": { - "value": { - "latest": "[variables('$fxv#15')]" + { + "name": "Set Export Dataset Version", + "description": "Save the dataset version from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "exportDatasetVersion", + "value": { + "value": "@activity('Read Manifest').output.firstRow.exportConfig.dataVersion", + "type": "Expression" + } } }, - "continueOnErrors": { - "value": "[parameters('continueOnErrors')]" - }, - "forceUpdateTag": { - "value": "[parameters('forceUpdateTag')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "1061109683489780517" + { + "name": "Detect Channel", + "description": "Determines what channel this export is from. Switch statement handles the different file types if the mcaColumnToCheck variable is set.", + "type": "Switch", + "dependsOn": [ + { + "activity": "Set Has No Rows", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set MCA Column", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Export Dataset Version", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "on": { + "value": "@if(or(empty(variables('mcaColumnToCheck')), variables('hasNoRows')), 'ignore', last(array(split(activity('Read Manifest').output.firstRow.blobs[0].blobName, '.'))))", + "type": "Expression" + }, + "cases": [ + { + "value": "csv", + "activities": [ + { + "name": "Check for MCA Column in CSV", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeExportContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel in CSV", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in CSV", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in CSV').output, 'firstRow'), contains(activity('Check for MCA Column in CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + }, + { + "value": "gz", + "activities": [ + { + "name": "Check for MCA Column in Gzip CSV", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "dataset": { + "referenceName": "[format('{0}_gzip', variables('safeExportContainerName'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel in Gzip CSV", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in Gzip CSV", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Gzip CSV').output, 'firstRow'), contains(activity('Check for MCA Column in Gzip CSV').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + }, + { + "value": "parquet", + "activities": [ + { + "name": "Check for MCA Column in Parquet", + "description": "Checks the dataset to determine if the applicable MCA-specific column exists.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "ParquetSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + }, + "dataset": { + "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@activity('Read Manifest').output.firstRow.blobs[0].blobName", + "type": "Expression" + } + } + } + } + }, + { + "name": "Set Schema File with Channel for Parquet", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check for MCA Column in Parquet", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), if(and(contains(activity('Check for MCA Column in Parquet').output, 'firstRow'), contains(activity('Check for MCA Column in Parquet').output.firstRow, variables('mcaColumnToCheck'))), '_mca', '_ea'), '.json'))", + "type": "Expression" + } + } + } + ] + } + ], + "defaultActivities": [ + { + "name": "Set Schema File", + "type": "SetVariable", + "dependsOn": [], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "schemaFile", + "value": { + "value": "@toLower(concat(variables('exportDatasetType'), '_', variables('exportDatasetVersion'), '.json'))", + "type": "Expression" + } + } + } + ] } }, - "parameters": { - "clusterName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer instance." + { + "name": "Set Scope", + "description": "Save the scope from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "databaseName": { - "type": "string", - "metadata": { - "description": "Required. Name of the FinOps hub Data Explorer database to create or update." + "userProperties": [], + "typeProperties": { + "variableName": "scope", + "value": { + "value": "@split(toLower(activity('Read Manifest').output.firstRow.exportConfig.resourceId), '/providers/microsoft.costmanagement/exports/')[0]", + "type": "Expression" } - }, - "scripts": { - "type": "object", - "metadata": { - "description": "Required. List of database scripts to run. The key is the name of the database script and the value is the KQL script content." + } + }, + { + "name": "Set Date", + "description": "Save the exported month from the export manifest.", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "continueOnErrors": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. If true, ingestion will continue even if some rows fail to ingest. Default: false." + "userProperties": [], + "typeProperties": { + "variableName": "date", + "value": { + "value": "@replace(substring(activity('Read Manifest').output.firstRow.runInfo.startDate, 0, 7), '-', '')", + "type": "Expression" } - }, - "forceUpdateTag": { - "type": "string", - "defaultValue": "[utcNow()]", - "metadata": { - "description": "Optional. Forces the table to be updated if different from the last time it was deployed." + } + }, + { + "name": "Failed to Read Manifest", + "type": "Fail", + "dependsOn": [ + { + "activity": "Set Date", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Scope", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Read Manifest", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Set Export Dataset Version", + "dependencyConditions": [ + "Failed" + ] + }, + { + "activity": "Detect Channel", + "dependencyConditions": [ + "Failed" + ] } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Failed to read the manifest file for this export run. Manifest path: ', pipeline().parameters.folderPath)", + "type": "Expression" + }, + "errorCode": "ManifestReadFailed" } }, - "resources": [ - { - "copy": { - "name": "cluster::database::script", - "count": "[length(items(parameters('scripts')))]" + { + "name": "Check Schema", + "description": "Verify that the schema file exists in storage.", + "type": "GetMetadata", + "dependsOn": [ + { + "activity": "Set Scope", + "dependencyConditions": [ + "Succeeded" + ] }, - "type": "Microsoft.Kusto/clusters/databases/scripts", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}/{2}', parameters('clusterName'), parameters('databaseName'), items(parameters('scripts'))[copyIndex()].key)]", - "properties": { - "scriptContent": "[items(parameters('scripts'))[copyIndex()].value]", - "continueOnErrors": "[parameters('continueOnErrors')]", - "forceUpdateTag": "[parameters('forceUpdateTag')]" + { + "activity": "Set Date", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Detect Channel", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@variables('schemaFile')", + "type": "Expression" + }, + "folderPath": "[format('{0}/schemas', parameters('configContainerName'))]" + } + }, + "fieldList": [ + "exists" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" } } - ] - } - }, - "dependsOn": [ - "cluster", - "cluster::hubDb", - "hub_VersionedScripts" - ] - }, - "getDataExplorerPrivateEndpointConnections": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetDataExplorerPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataExplorerName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "18030646605933559953" + }, + { + "name": "Schema Not Found", + "type": "Fail", + "dependsOn": [ + { + "activity": "Check Schema", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('The ', variables('schemaFile'), ' schema mapping file was not found. Please confirm version ', variables('exportDatasetVersion'), ' of the ', variables('exportDatasetType'), ' dataset is supported by this version of FinOps hubs. You may need to upgrade to a newer release. To add support for another dataset, you can create a custom mapping file.')", + "type": "Expression" + }, + "errorCode": "SchemaNotFound" } }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + { + "name": "Set Hub Dataset", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Set Export Dataset Type", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "dataExplorerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the ADX cluster." + "userProperties": [], + "typeProperties": { + "variableName": "hubDataset", + "value": { + "value": "@if(equals(toLower(variables('exportDatasetType')), 'focuscost'), 'Costs', if(equals(toLower(variables('exportDatasetType')), 'pricesheet'), 'Prices', if(equals(toLower(variables('exportDatasetType')), 'reservationdetails'), 'CommitmentDiscountUsage', if(equals(toLower(variables('exportDatasetType')), 'reservationrecommendations'), 'Recommendations', if(equals(toLower(variables('exportDatasetType')), 'reservationtransactions'), 'Transactions', if(equals(toLower(variables('exportDatasetType')), 'actualcost'), 'ActualCosts', if(equals(toLower(variables('exportDatasetType')), 'amortizedcost'), 'AmortizedCosts', toLower(variables('exportDatasetType')))))))))", + "type": "Expression" } } }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + { + "name": "Set Destination Folder", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Check Schema", + "dependencyConditions": [ + "Succeeded" + ] }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Kusto/clusters/privateEndpointConnections", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } + { + "activity": "Set Hub Dataset", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "destinationFolder", + "value": { + "value": "@replace(concat(variables('hubDataset'),'/',substring(variables('date'), 0, 4),'/',substring(variables('date'), 4, 2),'/',toLower(variables('scope')), if(equals(variables('hubDataset'), 'Recommendations'), activity('Read Manifest').output.firstRow.exportConfig.exportName, '')),'//','/')", + "type": "Expression" } } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" - } - } - } - }, - "dependsOn": [ - "cluster", - "dataFactoryVNet::dataExplorerManagedPrivateEndpoint" - ] - }, - "approveDataExplorerPrivateEndpointConnections": { - "condition": "[and(variables('useAzure'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveDataExplorerPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "dataExplorerName": { - "value": "[replace(parameters('clusterName'), '_', '-')]" }, - "privateEndpointConnections": { - "value": "[reference('getDataExplorerPrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "18030646605933559953" + { + "name": "For Each Blob", + "description": "Loop thru each exported file listed in the manifest.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Set Destination Folder", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(variables('hasNoRows'), json('[]'), activity('Read Manifest').output.firstRow.blobs)", + "type": "Expression" + }, + "batchCount": "[if(parameters('enablePublicAccess'), 30, 4)]", + "isSequential": false, + "activities": [ + { + "name": "Execute", + "description": "Run the ingestion ETL pipeline.", + "type": "ExecutePipeline", + "dependsOn": [], + "policy": { + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_ETL_{1}', variables('safeExportContainerName'), variables('safeIngestionContainerName'))]", + "type": "PipelineReference" + }, + "waitOnCompletion": true, + "parameters": { + "blobPath": { + "value": "@item().blobName", + "type": "Expression" + }, + "destinationFolder": { + "value": "@variables('destinationFolder')", + "type": "Expression" + }, + "destinationFile": { + "value": "@last(array(split(replace(replace(item().blobName, '.gz', ''), '.csv', '.parquet'), '/')))", + "type": "Expression" + }, + "ingestionId": { + "value": "@activity('Read Manifest').output.firstRow.runInfo.runId", + "type": "Expression" + }, + "schemaFile": { + "value": "@variables('schemaFile')", + "type": "Expression" + }, + "exportDatasetType": { + "value": "@variables('exportDatasetType')", + "type": "Expression" + }, + "exportDatasetVersion": { + "value": "@variables('exportDatasetVersion')", + "type": "Expression" + } + } + } + } + ] } }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + { + "name": "Copy Manifest", + "description": "Copy the manifest to the ingestion container to trigger ADX ingestion", + "type": "Copy", + "dependsOn": [ + { + "activity": "For Each Blob", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "dataExplorerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the ADX cluster." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Kusto/clusters/privateEndpointConnections", - "apiVersion": "2023-08-15", - "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" + "sink": { + "type": "JsonSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "JsonWriteSettings" + } + }, + "enableStaging": false + }, + "inputs": [ + { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": "manifest.json", + "folderPath": { + "value": "@pipeline().parameters.folderPath", + "type": "Expression" + } } } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" - } + ], + "outputs": [ + { + "referenceName": "manifest", + "type": "DatasetReference", + "parameters": { + "fileName": "manifest.json", + "folderPath": { + "value": "[format('@concat(''{0}/'', variables(''destinationFolder''))', parameters('ingestionContainerName'))]", + "type": "Expression" + } + } + } + ] + } + ], + "parameters": { + "folderPath": { + "type": "string" + }, + "fileName": { + "type": "string" } - } - }, - "dependsOn": [ - "cluster", - "getDataExplorerPrivateEndpointConnections" - ] - }, - "trigger_IngestionManifestAdded": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Core_IngestionManifestAddedTrigger", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" }, - "mode": "Incremental", - "parameters": { - "dataFactoryName": { - "value": "[parameters('app').dataFactory]" + "variables": { + "date": { + "type": "String" }, - "triggerName": { - "value": "[format('{0}_ManifestAdded', variables('INGESTION'))]" + "destinationFolder": { + "type": "String" }, - "pipelineName": { - "value": "[format('{0}_ExecuteETL', variables('INGESTION'))]" + "exportDatasetType": { + "type": "String" }, - "pipelineParameters": { - "value": { - "folderPath": "@triggerBody().folderPath" - } + "exportDatasetVersion": { + "type": "String" }, - "storageAccountName": { - "value": "[parameters('app').storage]" + "hasNoRows": { + "type": "Boolean" }, - "storageContainer": { - "value": "[variables('INGESTION')]" + "hubDataset": { + "type": "String" }, - "storagePathEndsWith": { - "value": "manifest.json" + "mcaColumnToCheck": { + "type": "String" + }, + "schemaFile": { + "type": "String" + }, + "scope": { + "type": "String" } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "14264521107451792604" - } - }, - "parameters": { - "dataFactoryName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Data Factory instance." - } + "annotations": [ + "New export" + ] + }, + "dependsOn": [ + "dataset_config", + "dataset_manifest", + "dataset_msexports", + "dataset_msexports_gzip", + "dataset_msexports_parquet", + "pipeline_ToIngestion" + ], + "metadata": { + "description": "Queues the msexports_ETL_ingestion pipeline." + } + }, + "pipeline_ToIngestion": { + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ETL_{1}', variables('safeExportContainerName'), variables('safeIngestionContainerName')))]", + "properties": { + "activities": [ + { + "name": "Get Existing Parquet Files", + "description": "Get the previously ingested files so we can remove any older data. This is necessary to avoid data duplication in reports.", + "type": "GetMetadata", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "triggerName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory trigger to create or update." + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[format('{0}_files', variables('safeIngestionContainerName'))]", + "type": "DatasetReference", + "parameters": { + "folderPath": "@pipeline().parameters.destinationFolder" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" } - }, - "storageAccountName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + { + "name": "Filter Out Current Exports", + "description": "Remove existing files from the current export so those files do not get deleted.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Get Existing Parquet Files", + "dependencyConditions": [ + "Completed" + ] } - }, - "storageContainer": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Azure storage container to monitor for updates and trigger events for." + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", + "type": "Expression" + }, + "condition": { + "value": "[format('@and(endswith(item().name, ''.parquet''), not(startswith(item().name, concat(pipeline().parameters.ingestionId, ''{0}''))))', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" } + } + }, + { + "name": "Load Schema Mappings", + "description": "Get schema mapping file to use for the CSV to parquet conversion.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "storagePathStartsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "JsonReadSettings" + } + }, + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": { + "value": "@toLower(pipeline().parameters.schemaFile)", + "type": "Expression" + }, + "folderPath": "[format('{0}/schemas', parameters('configContainerName'))]" + } } - }, - "storagePathEndsWith": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } + }, + { + "name": "Failed to Load Schema", + "type": "Fail", + "dependsOn": [ + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Failed" + ] } - }, - "pipelineName": { - "type": "string", - "metadata": { - "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to load the ', pipeline().parameters.schemaFile, ' schema file. Please confirm the schema and version are supported for FinOps hubs ingestion. Unsupported files will remain in the msexports container.')", + "type": "Expression" + }, + "errorCode": "SchemaLoadFailed" + } + }, + { + "name": "Set Additional Columns", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "pipelineParameters": { - "type": "object", - "metadata": { - "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + "userProperties": [], + "typeProperties": { + "variableName": "additionalColumns", + "value": { + "value": "@intersection(array(json(concat('[{\"name\":\"x_SourceProvider\",\"value\":\"Microsoft\"},{\"name\":\"x_SourceName\",\"value\":\"Cost Management\"},{\"name\":\"x_SourceType\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"},{\"name\":\"x_SourceVersion\",\"value\":\"', pipeline().parameters.exportDatasetVersion, '\"}'))), activity('Load Schema Mappings').output.firstRow.additionalColumns)", + "type": "Expression" } } }, - "resources": [ - { - "condition": "[not(empty(parameters('storageAccountName')))]", - "type": "Microsoft.DataFactory/factories/triggers", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", - "properties": { - "annotations": [], - "pipelines": [ - { - "pipelineReference": { - "referenceName": "[parameters('pipelineName')]", - "type": "PipelineReference" + { + "name": "For Each Old File", + "description": "Loop thru each of the existing files from previous exports.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Convert to Parquet", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Filter Out Current Exports", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@activity('Filter Out Current Exports').output.Value", + "type": "Expression" + }, + "activities": [ + { + "name": "Delete Old Ingested File", + "description": "Delete the previously ingested files from older exports.", + "type": "Delete", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[variables('safeIngestionContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@concat(pipeline().parameters.destinationFolder, '/', item().name)", + "type": "Expression" + } + } }, - "parameters": "[parameters('pipelineParameters')]" + "enableLogging": false, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false + } } - ], - "type": "BlobEventsTrigger", - "typeProperties": { - "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", - "blobPathEndsWith": "[parameters('storagePathEndsWith')]", - "ignoreEmptyBlobs": true, - "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", - "events": [ - "Microsoft.Storage.BlobCreated" - ] } - } + ] } - ] - } - }, - "dependsOn": [ - "appRegistration", - "pipeline_ExecuteIngestionETL" - ] - }, - "runInitializationPipeline": { - "condition": "[or(variables('useAzure'), variables('useFabric'))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.Analytics_InitializeHub", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "dataFactoryInstances": { - "value": [ - "[parameters('app').dataFactory]" - ] - }, - "identityName": { - "value": "[reference('appRegistration').outputs.triggerManagerIdentityName.value]" }, - "startPipelines": { - "value": [ - "[format('{0}_InitializeHub', variables('CONFIG'))]" - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4749940909471549408" + { + "name": "Set Destination Path", + "type": "SetVariable", + "dependsOn": [], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "destinationPath", + "value": { + "value": "[format('@concat(pipeline().parameters.destinationFolder, ''/'', pipeline().parameters.ingestionId, ''{0}'', pipeline().parameters.destinationFile)', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" + } } }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" + { + "name": "Convert to Parquet", + "description": "[format('Convert CSV to parquet and move the file to the {0} container.', parameters('ingestionContainerName'))]", + "type": "Switch", + "dependsOn": [ + { + "activity": "Set Destination Path", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Load Schema Mappings", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Set Additional Columns", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "on": { + "value": "@last(array(split(pipeline().parameters.blobPath, '.')))", + "type": "Expression" + }, + "cases": [ + { + "value": "csv", + "activities": [ + { + "name": "Convert CSV File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:10:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false, + "translator": { + "value": "@activity('Load Schema Mappings').output.firstRow.translator", + "type": "Expression" + } + }, + "inputs": [ + { + "referenceName": "[variables('safeExportContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('safeIngestionContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" + ] }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + { + "value": "gz", + "activities": [ + { + "name": "Convert GZip CSV File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:10:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "DelimitedTextSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "DelimitedTextReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false, + "translator": { + "value": "@activity('Load Schema Mappings').output.firstRow.translator", + "type": "Expression" + } + }, + "inputs": [ + { + "referenceName": "[format('{0}_gzip', variables('safeExportContainerName'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('safeIngestionContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] } - } + ] }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" + { + "value": "parquet", + "activities": [ + { + "name": "Move Parquet File", + "type": "Copy", + "dependsOn": [], + "policy": { + "timeout": "0.00:05:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "source": { + "type": "ParquetSource", + "additionalColumns": { + "value": "@variables('additionalColumns')", + "type": "Expression" + }, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + }, + "sink": { + "type": "ParquetSink", + "storeSettings": { + "type": "AzureBlobFSWriteSettings" + }, + "formatSettings": { + "type": "ParquetWriteSettings", + "fileExtension": ".parquet" + } + }, + "enableStaging": false, + "parallelCopies": 1, + "validateDataConsistency": false + }, + "inputs": [ + { + "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + } + ], + "outputs": [ + { + "referenceName": "[variables('safeIngestionContainerName')]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@variables('destinationPath')", + "type": "Expression" + } + } + } + ] } - } + ] } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + ], + "defaultActivities": [ + { + "name": "Unsupported File Type", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to ingest the specified export file because the file type is not supported. File: ', pipeline().parameters.blobPath)", + "type": "Expression" + }, + "errorCode": "UnsupportedExportFileType" + } } - } + ] + } + }, + { + "name": "Read Hub Config", + "description": "Read the hub config to determine if the export should be retained.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false }, - "name": { - "type": "string" + "formatSettings": { + "type": "JsonReadSettings" } }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": "settings.json", + "folderPath": "[parameters('configContainerName')]" } } + } + }, + { + "name": "If Not Retaining Exports", + "description": "If the msexports retention period <= 0, delete the source file. The main reason to keep the source file is to allow for troubleshooting and reprocessing in the future.", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Convert to Parquet", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Read Hub Config", + "dependencyConditions": [ + "Completed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@lessOrEquals(coalesce(activity('Read Hub Config').output.firstRow.retention.msexports.days, 0), 0)", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Delete Source File", + "description": "Delete the exported data file to keep storage costs down. This file is not referenced by any reporting systems.", + "type": "Delete", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[format('{0}_parquet', variables('safeExportContainerName'))]", + "type": "DatasetReference", + "parameters": { + "blobPath": { + "value": "@pipeline().parameters.blobPath", + "type": "Expression" + } + } + }, + "enableLogging": false, + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": true, + "enablePartitionDiscovery": false + } + } + } + ] + } + } + ], + "parameters": { + "blobPath": { + "type": "String" + }, + "destinationFile": { + "type": "string" + }, + "destinationFolder": { + "type": "string" + }, + "ingestionId": { + "type": "string" + }, + "schemaFile": { + "type": "string" + }, + "exportDatasetType": { + "type": "string" + }, + "exportDatasetVersion": { + "type": "string" + } + }, + "variables": { + "additionalColumns": { + "type": "Array" + }, + "destinationPath": { + "type": "String" + } + }, + "annotations": [] + }, + "dependsOn": [ + "dataset_config", + "dataset_ingestion", + "dataset_ingestion_files", + "dataset_msexports", + "dataset_msexports_gzip", + "dataset_msexports_parquet" + ], + "metadata": { + "description": "Transforms CSV data to a standard schema and converts to Parquet." + } + }, + "pipeline_ToDataExplorer": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ETL_dataExplorer', variables('safeIngestionContainerName')))]", + "properties": { + "activities": [ + { + "name": "Read Hub Config", + "description": "Read the hub config to determine how long data should be retained.", + "type": "Lookup", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" + "userProperties": [], + "typeProperties": { + "source": { + "type": "JsonSource", + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "recursive": false, + "enablePartitionDiscovery": false }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "formatSettings": { + "type": "JsonReadSettings" } }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "dataset": { + "referenceName": "[variables('safeConfigContainerName')]", + "type": "DatasetReference", + "parameters": { + "fileName": "settings.json", + "folderPath": "[parameters('configContainerName')]" } } } }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "dataFactoryInstances": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. List of Azure Data Factory instances to start triggers for. Can be up to 1 per publisher." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to use when starting the triggers." + { + "name": "Set Final Retention Months", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Read Hub Config", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "startAllTriggers": { - "type": "bool", - "defaultValue": false, - "metadata": { - "description": "Optional. Start all triggers for the Data Factory instances. Default: false." + "userProperties": [], + "typeProperties": { + "variableName": "finalRetentionMonths", + "value": { + "value": "@coalesce(activity('Read Hub Config').output.firstRow.retention.final.months, 999)", + "type": "Expression" } - }, - "startPipelines": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. List of pipelines to run. Default: [] (no pipelines)." + } + }, + { + "name": "Until Capacity Is Available", + "type": "Until", + "dependsOn": [ + { + "activity": "Set Final Retention Months", + "dependencyConditions": [ + "Completed", + "Skipped" + ] } - } - }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "uniqueInstances": "[union(filter(parameters('dataFactoryInstances'), lambda('adf', not(empty(lambdaVariables('adf'))))), createArray())]" - }, - "resources": { - "initialize": { - "copy": { - "name": "initialize", - "count": "[length(variables('uniqueInstances'))]" + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@equals(variables('tryAgain'), false)", + "type": "Expression" }, - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[if(lessOrEquals(length(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()])), 64), format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), substring(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), 0, 64))]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[parameters('identityName')]" + "activities": [ + { + "name": "Confirm Ingestion Capacity", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "scriptContent": { - "value": "[variables('$fxv#0')]" + "userProperties": [], + "typeProperties": { + "command": ".show capacity | where Resource == 'Ingestions' | project Remaining", + "commandTimeout": "00:20:00" }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[variables('uniqueInstances')[copyIndex()]]" - }, - { - "name": "Pipelines", - "value": "[join(parameters('startPipelines'), '|')]" - }, - { - "name": "StartAllTriggers", - "value": "[string(parameters('startAllTriggers'))]" - } - ] + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference" } }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + { + "name": "If Has Capacity", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Confirm Ingestion Capacity", + "dependencyConditions": [ + "Succeeded" + ] } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@or(equals(activity('Confirm Ingestion Capacity').output.count, 0), greater(activity('Confirm Ingestion Capacity').output.value[0].Remaining, 0))", + "type": "Expression" }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } + "ifFalseActivities": [ + { + "name": "Wait for Ingestion", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 15 } }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." + { + "name": "Try Again", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait for Ingestion", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": true } } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" + ], + "ifTrueActivities": [ + { + "name": "Pre-Ingest Cleanup", + "description": "Cost Management exports include all month-to-date data from the previous export run. To ensure data is not double-reported, it must be dropped from the raw table before ingestion completes. Remove previous ingestions into the raw table for the month and any previous runs of the current ingestion month file in any table.", + "type": "AzureDataExplorerCommand", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "scriptStorage": { - "type": "string" + "typeProperties": { + "command": { + "value": "@concat('.drop extents <| .show extents | where (TableName == \"', pipeline().parameters.table, '\" and Tags !has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '\") or (Tags has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '/', pipeline().parameters.originalFileName, '\")')", + "type": "Expression" + }, + "commandTimeout": "00:20:00" }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } + } + }, + { + "name": "Ingest Data", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Pre-Ingest Cleanup", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 3, + "retryIntervalInSeconds": 120, + "secureOutput": false, + "secureInput": false }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } + "userProperties": [], + "typeProperties": { + "command": { + "value": "[format('@concat(''.ingest into table '', pipeline().parameters.table, '' (\"abfss://{0}@{1}.dfs.{2}/'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.fileName, '';{3}\") with (format=\"parquet\", ingestionMappingReference=\"'', pipeline().parameters.table, ''_mapping\", tags=\"[\\\"drop-by:'', pipeline().parameters.ingestionId, ''\\\", \\\"drop-by:'', pipeline().parameters.folderPath, ''/'', pipeline().parameters.originalFileName, ''\\\", \\\"drop-by:ftk-version-{4}\\\"]\"); print Success = assert(iff(toscalar($command_results | project-keep HasErrors) == false, true, false), \"Ingestion Failed\")'')', parameters('ingestionContainerName'), parameters('storageAccountName'), environment().suffixes.storage, if(variables('useFabric'), 'impersonate', 'managed_identity=system'), variables('ftkVersion'))]", + "type": "Expression" + }, + "commandTimeout": "01:00:00" + }, + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" } } }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." + { + "name": "Post-Ingest Cleanup", + "description": "Cost Management exports include all month-to-date data from the previous export run. To ensure data is not double-reported, it must be dropped after ingestion completes. Remove the current ingestion month file from raw and any old ingestions for the month from the final table.", + "type": "AzureDataExplorerCommand", + "dependsOn": [ + { + "activity": "Ingest Data", + "dependencyConditions": [ + "Completed" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." + "typeProperties": { + "command": { + "value": "@concat('.drop extents <| .show extents | extend isOldFinalData = (TableName startswith \"', replace(pipeline().parameters.table, '_raw', '_final_v'), '\" and Tags !has \"drop-by:', pipeline().parameters.ingestionId, '\" and Tags has \"drop-by:', pipeline().parameters.folderPath, '\") | extend isPastFinalRetention = (TableName startswith \"', replace(pipeline().parameters.table, '_raw', '_final_v'), '\" and todatetime(substring(strcat(replace_string(extract(\"drop-by:[A-Za-z]+/(\\\\d{4}/\\\\d{2}(/\\\\d{2})?)\", 1, Tags), \"/\", \"-\"), \"-01\"), 0, 10)) < datetime_add(\"month\", -', if(lessOrEquals(variables('finalRetentionMonths'), 0), 0, variables('finalRetentionMonths')), ', startofmonth(now()))) | where isOldFinalData or isPastFinalRetention')", + "type": "Expression" + }, + "commandTimeout": "00:20:00" }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "linkedServiceName": { + "referenceName": "[variables('hubDataExplorerName')]", + "type": "LinkedServiceReference", + "parameters": { + "database": "[parameters('dataExplorerIngestionDatabase')]" + } } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" + }, + { + "name": "Ingestion Complete", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Post-Ingest Cleanup", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "name": { - "type": "string" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false } }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" + { + "name": "Abort On Ingestion Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Ingest Data", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false } }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + { + "name": "Ingestion Failed Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Abort On Ingestion Error", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Data Explorer ingestion into the ', pipeline().parameters.table, ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Ingest Data').output.errors), 0), activity('Ingest Data').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Ingest Data').output.errors), 0), activity('Ingest Data').output.errors[0].Code, 'None'), ')')", + "type": "Expression" + }, + "errorCode": "DataExplorerIngestionFailed" + } + }, + { + "name": "Abort On Pre-Ingest Drop Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Pre-Ingest Cleanup", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false + } + }, + { + "name": "Pre-Ingest Drop Failed Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Abort On Pre-Ingest Drop Error", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Data Explorer pre-ingestion cleanup (drop extents from raw table) for the ', pipeline().parameters.table, ' table failed. Ingestion was not completed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Pre-Ingest Cleanup').output.errors), 0), activity('Pre-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", + "type": "Expression" + }, + "errorCode": "DataExplorerPreIngestionDropFailed" } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + { + "name": "Abort On Post-Ingest Drop Error", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Post-Ingest Cleanup", + "dependencyConditions": [ + "Failed" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "tryAgain", + "value": false + } + }, + { + "name": "Post-Ingest Drop Failed Error", + "type": "Fail", + "dependsOn": [ + { + "activity": "Abort On Post-Ingest Drop Error", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Data Explorer post-ingestion cleanup (drop extents from final tables) for the ', replace(pipeline().parameters.table, '_raw', '_final_*'), ' table failed. Please fix the error and rerun ingestion for the following folder path: \"', pipeline().parameters.folderPath, '\". File: ', pipeline().parameters.originalFileName, '. Error: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Message, 'Unknown'), ' (Code: ', if(greater(length(activity('Post-Ingest Cleanup').output.errors), 0), activity('Post-Ingest Cleanup').output.errors[0].Code, 'None'), ')')", + "type": "Expression" + }, + "errorCode": "DataExplorerPostIngestionDropFailed" + } } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + ] + } + } + ], + "timeout": "0.02:00:00" + } + } + ], + "parameters": { + "folderPath": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "originalFileName": { + "type": "string" + }, + "ingestionId": { + "type": "string" + }, + "table": { + "type": "string" + } + }, + "variables": { + "tryAgain": { + "type": "Boolean", + "defaultValue": true + }, + "logRetentionDays": { + "type": "Integer", + "defaultValue": 0 + }, + "finalRetentionMonths": { + "type": "Integer", + "defaultValue": 999 + } + }, + "annotations": [] + }, + "dependsOn": [ + "dataset_config", + "linkedService_dataExplorer" + ], + "metadata": { + "description": "Ingests parquet data into an Azure Data Explorer cluster." + } + }, + "pipeline_ExecuteIngestionETL": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.DataFactory/factories/pipelines", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), format('{0}_ExecuteETL', variables('safeIngestionContainerName')))]", + "properties": { + "concurrency": 1, + "activities": [ + { + "name": "Wait", + "description": "Files may not be available immediately after being created.", + "type": "Wait", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 60 + } + }, + { + "name": "Set Container Folder Path", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "containerFolderPath", + "value": { + "value": "@join(skip(array(split(pipeline().parameters.folderPath, '/')), 1), '/')", + "type": "Expression" + } + } + }, + { + "name": "Get Existing Parquet Files", + "description": "Get the previously ingested files so we can get file paths.", + "type": "GetMetadata", + "dependsOn": [ + { + "activity": "Set Container Folder Path", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "dataset": { + "referenceName": "[format('{0}_files', variables('safeIngestionContainerName'))]", + "type": "DatasetReference", + "parameters": { + "folderPath": "@variables('containerFolderPath')" + } + }, + "fieldList": [ + "childItems" + ], + "storeSettings": { + "type": "AzureBlobFSReadSettings", + "enablePartitionDiscovery": false + }, + "formatSettings": { + "type": "ParquetReadSettings" + } + } + }, + { + "name": "Filter Out Folders", + "description": "Remove any folders or manifest files.", + "type": "Filter", + "dependsOn": [ + { + "activity": "Get Existing Parquet Files", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "items": { + "value": "@if(contains(activity('Get Existing Parquet Files').output, 'childItems'), activity('Get Existing Parquet Files').output.childItems, json('[]'))", + "type": "Expression" + }, + "condition": { + "value": "@and(equals(item().type, 'File'), not(contains(toLower(item().name), 'manifest.json')))", + "type": "Expression" + } + } + }, + { + "name": "Set Ingestion Timestamp", + "type": "SetVariable", + "dependsOn": [ + { + "activity": "Wait", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "variableName": "timestamp", + "value": { + "value": "@utcNow()", + "type": "Expression" + } + } + }, + { + "name": "For Each Old File", + "description": "Loop thru each of the existing files.", + "type": "ForEach", + "dependsOn": [ + { + "activity": "Filter Out Folders", + "dependencyConditions": [ + "Succeeded" + ] + }, + { + "activity": "Data Explorer validation", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "batchCount": "[parameters('dataExplorerIngestionCapacity')]", + "items": { + "value": "@activity('Filter Out Folders').output.Value", + "type": "Expression" + }, + "activities": [ + { + "name": "Execute", + "description": "Run the ADX ETL pipeline.", + "type": "ExecutePipeline", + "dependsOn": [], + "policy": { + "secureInput": false }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + "userProperties": [], + "typeProperties": { + "pipeline": { + "referenceName": "[format('{0}_ETL_dataExplorer', variables('safeIngestionContainerName'))]", + "type": "PipelineReference" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" + "waitOnCompletion": true, + "parameters": { + "folderPath": { + "value": "@variables('containerFolderPath')", + "type": "Expression" }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" + "fileName": { + "value": "@item().name", + "type": "Expression" }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } + "originalFileName": { + "value": "[format('@last(array(split(item().name, ''{0}'')))', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "ingestionId": { + "value": "[format('@concat(first(array(split(item().name, ''{0}''))), ''_'', variables(''timestamp''))', variables('ingestionIdFileNameSeparator'))]", + "type": "Expression" + }, + "table": { + "value": "@concat(first(array(split(variables('containerFolderPath'), '/'))), '_raw')", + "type": "Expression" + } } } } + ] + } + }, + { + "name": "If No Files", + "description": "If there are no files found, fail the pipeline.", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Filter Out Folders", + "dependencyConditions": [ + "Succeeded" + ] } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "@equals(length(activity('Filter Out Folders').output.Value), 0)", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Files Not Found", + "type": "Fail", + "dependsOn": [], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Unable to locate parquet files to ingest from the ', pipeline().parameters.folderPath, ' path. Please confirm the folder path is the full path, including the \"ingestion\" container and not starting with or ending with a slash (\"/\").')", + "type": "Expression" + }, + "errorCode": "IngestionFilesNotFound" + } + } + ] } - } - } - }, - "dependsOn": [ - "appRegistration", - "pipeline_InitializeHub" - ] - } - }, - "outputs": { - "clusterId": { - "type": "string", - "metadata": { - "description": "The resource ID of the cluster." - }, - "value": "[if(variables('useFabric'), '', resourceId('Microsoft.Kusto/clusters', replace(parameters('clusterName'), '_', '-')))]" - }, - "principalId": { - "type": "string", - "metadata": { - "description": "The ID of the cluster system assigned managed identity." - }, - "value": "[if(variables('useFabric'), '', reference('cluster', '2023-08-15', 'full').identity.principalId)]" - }, - "clusterName": { - "type": "string", - "metadata": { - "description": "The name of the cluster." - }, - "value": "[if(variables('useFabric'), '', replace(parameters('clusterName'), '_', '-'))]" - }, - "clusterUri": { - "type": "string", - "metadata": { - "description": "The URI of the cluster." - }, - "value": "[variables('dataExplorerUri')]" - }, - "ingestionDbName": { - "type": "string", - "metadata": { - "description": "The name of the database for data ingestion." - }, - "value": "[variables('INGESTION_DB')]" - }, - "hubDbName": { - "type": "string", - "metadata": { - "description": "The name of the database for queries." - }, - "value": "[variables('HUB_DB')]" - }, - "clusterIngestionCapacity": { - "type": "int", - "metadata": { - "description": "Max ingestion capacity of the cluster." - }, - "value": "[variables('dataExplorerIngestionCapacity')]" - } - } - } - }, - "dependsOn": [ - "cmExports", - "core", - "deleteOldResources" - ] - }, - "remoteHub": { - "condition": "[not(empty(parameters('remoteHubStorageKey')))]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.RemoteHub", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[__bicep.newApp(variables('hub'), 'Microsoft.FinOpsHubs', 'RemoteHub')]" - }, - "remoteStorageKey": { - "value": "[parameters('remoteHubStorageKey')]" - }, - "remoteHubStorageUri": { - "value": "[parameters('remoteHubStorageUri')]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "3199707033377872229" - } - }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" + }, + { + "name": "Data Explorer validation", + "description": "If Data Explorer is stopped, start it", + "type": "IfCondition", + "dependsOn": [ + { + "activity": "Set Ingestion Timestamp", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "userProperties": [], + "typeProperties": { + "expression": { + "value": "[format('@equals({0}, true)', variables('deployDataExplorer'))]", + "type": "Expression" + }, + "ifTrueActivities": [ + { + "name": "Start ADX Cluster", + "type": "WebActivity", + "dependsOn": [], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "method": "POST", + "url": { + "value": "[format('{0}{1}/start?api-version=2024-04-13', environment().resourceManager, resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')))]", + "type": "Expression" + }, + "body": "{}", + "authentication": { + "type": "MSI", + "resource": { + "value": "[environment().resourceManager]", + "type": "Expression" + } + } + } + }, + { + "name": "Error ADX Start", + "type": "Fail", + "dependsOn": [ + { + "activity": "Start ADX Cluster After Error", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "message": { + "value": "@concat('Failed to start the Data Explorer instance. Message: ', activity('Start ADX Cluster After Error').output.error.message)", + "type": "Expression" + }, + "errorCode": { + "value": "@activity('Start ADX Cluster After Error').output.error.code", + "type": "Expression" + } + } + }, + { + "name": "Wait ADX Provision State", + "type": "Wait", + "dependsOn": [ + { + "activity": "Start ADX Cluster", + "dependencyConditions": [ + "Failed" + ] + } + ], + "userProperties": [], + "typeProperties": { + "waitTimeInSeconds": 600 + } + }, + { + "name": "Start ADX Cluster After Error", + "type": "WebActivity", + "dependsOn": [ + { + "activity": "Wait ADX Provision State", + "dependencyConditions": [ + "Succeeded" + ] + } + ], + "policy": { + "timeout": "0.12:00:00", + "retry": 0, + "retryIntervalInSeconds": 30, + "secureOutput": false, + "secureInput": false + }, + "userProperties": [], + "typeProperties": { + "method": "POST", + "url": { + "value": "[format('{0}{1}/start?api-version=2024-04-13', environment().resourceManager, resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')))]", + "type": "Expression", + "body": "{}" + }, + "authentication": { + "type": "MSI", + "resource": { + "value": "[environment().resourceManager]", + "type": "Expression" + } + } + } + } + ] } } + ], + "parameters": { + "folderPath": { + "type": "string" + } }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } + "variables": { + "containerFolderPath": { + "type": "string" + }, + "timestamp": { + "type": "string" } - } + }, + "annotations": [ + "New ingestion" + ] }, + "dependsOn": [ + "dataset_ingestion_files", + "pipeline_ToDataExplorer" + ], "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } + "description": "Queues the ingestion_ETL_dataExplorer pipeline to account for Data Factory pipeline trigger limits." } }, - "_1.HubRoutingProperties": { - "type": "object", + "azuretimezones": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "azuretimezones", "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "scriptStorage": { - "type": "string" + "mode": "Incremental", + "parameters": { + "location": { + "value": "[parameters('location')]" + } }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "4022825617953122148" + } + }, + "parameters": { + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The location to use for the managed identity and deployment script to auto-start triggers. Default = (resource group location)." + } }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "timezoneobject": { + "type": "object", + "defaultValue": { + "australiaeast": "AUS Eastern Standard Time", + "australiacentral": "AUS Eastern Standard Time", + "australiacentral2": "AUS Eastern Standard Time", + "australiasoutheast": "AUS Eastern Standard Time", + "brazilsouth": "E. South America Standard Time", + "canadacentral": "Central Standard Time", + "canadaeast": "Eastern Standard Time", + "centralindia": "India Standard Time", + "centralus": "Central Standard Time", + "eastasia": "China Standard Time", + "eastus": "Eastern Standard Time", + "eastus2": "Eastern Standard Time", + "francecentral": "W. Europe Standard Time", + "germanynorth": "W. Europe Standard Time", + "germanywestcentral": "W. Europe Standard Time", + "japaneast": "Japan Standard Time", + "japanwest": "Japan Standard Time", + "koreacentral": "Korea Standard Time", + "koreasouth": "Korea Standard Time", + "northcentralus": "Central Standard Time", + "northeurope": "GMT Standard Time", + "norwayeast": "W. Europe Standard Time", + "norwaywest": "W. Europe Standard Time", + "southcentralus": "Central Standard Time", + "southindia": "India Standard Time", + "southeastasia": "Singapore Standard Time", + "switzerlandnorth": "W. Europe Standard Time", + "switzerlandwest": "W. Europe Standard Time", + "uksouth": "GMT Standard Time", + "ukwest": "GMT Standard Time", + "westcentralus": "Central Standard Time", + "westeurope": "W. Europe Standard Time", + "westindia": "India Standard Time", + "westus": "Pacific Standard Time", + "westus2": "Pacific Standard Time" + } }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "utchrs": { + "type": "string", + "defaultValue": "[utcNow('hh')]" }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "utcmins": { + "type": "string", + "defaultValue": "[utcNow('mm')]" + }, + "utcsecs": { + "type": "string", + "defaultValue": "[utcNow('ss')]" } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" + }, + "variables": { + "loc": "[toLower(replace(parameters('location'), ' ', ''))]", + "timezone": "[coalesce(tryGet(parameters('timezoneobject'), variables('loc')), 'Universal Coordinated Time')]" + }, + "resources": [], + "outputs": { + "AzureRegion": { + "type": "string", + "value": "[parameters('location')]" }, - "dataFactory": { - "type": "string" + "Timezone": { + "type": "string", + "value": "[variables('timezone')]" }, - "keyVault": { - "type": "string" + "UtcHours": { + "type": "string", + "value": "[parameters('utchrs')]" }, - "scripts": { - "type": "string" + "UtcMinutes": { + "type": "string", + "value": "[parameters('utcmins')]" }, - "storage": { - "type": "string" + "UtcSeconds": { + "type": "string", + "value": "[parameters('utcsecs')]" } } } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } } }, - "HubAppProperties": { - "type": "object", + "getStoragePrivateEndpointConnections": { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "GetStoragePrivateEndpointConnections", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" + "expressionEvaluationOptions": { + "scope": "inner" }, - "storage": { - "type": "string" + "mode": "Incremental", + "parameters": { + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + } }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" - } - } - } - }, - "functions": [ - { - "namespace": "__bicep", - "members": { - "privateRoutingForLinkedServices": { - "parameters": [ + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "491732910990436410" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ { - "$ref": "#/definitions/_1.HubProperties", - "name": "hub" + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } } ], - "output": { - "type": "object", - "value": "[if(parameters('hub').options.privateRouting, createObject('connectVia', createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference')), createObject())]" - }, - "metadata": { - "description": "Returns an object that represents the properties needed to enable private routing for linked services. Use property expansion (`...value`) to apply to a linkedServices resource.", - "__bicep_imported_from!": { - "sourceTemplate": "../../fx/hub-types.bicep" + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" } } } - } - } - ], - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "remoteStorageKey": { - "type": "securestring", - "metadata": { - "description": "Required. Create and store a key for a remote storage account." - } - }, - "remoteHubStorageUri": { - "type": "string", - "metadata": { - "description": "Required. Remote storage account for ingestion dataset." - } - }, - "ingestionContainerName": { - "type": "string", - "defaultValue": "ingestion", - "metadata": { - "description": "Optional. Name of the ingestion container. Default: ingestion." - } - } - }, - "variables": { - "storageKeySecretName": "[format('{0}-storage-key', toLower(parameters('app').hub.name))]", - "finOpsToolkitVersion": "12.0" - }, - "resources": { - "dataFactory::linkedService_remoteHubStorage": { - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'remoteHubStorage')]", - "properties": "[shallowMerge(createArray(createObject('annotations', createArray(), 'parameters', createObject(), 'type', 'AzureBlobFS', 'typeProperties', createObject('url', parameters('remoteHubStorageUri'), 'accountKey', createObject('type', 'AzureKeyVaultSecret', 'store', createObject('referenceName', parameters('app').keyVault, 'type', 'LinkedServiceReference'), 'secretName', variables('storageKeySecretName')))), __bicep.privateRoutingForLinkedServices(parameters('app').hub)))]" - }, - "dataFactory::dataset_ingestion": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('ingestionContainerName'))]", - "properties": { - "annotations": [], - "parameters": { - "blobPath": { - "type": "String" - } - }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileName": { - "value": "@{dataset().blobPath}", - "type": "Expression" - }, - "fileSystem": "[parameters('ingestionContainerName')]" - } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "remoteHubStorage", - "type": "LinkedServiceReference" - } }, "dependsOn": [ - "dataFactory::linkedService_remoteHubStorage" + "storageManagedPrivateEndpoint" ] }, - "dataFactory::dataset_ingestion_files": { - "type": "Microsoft.DataFactory/factories/datasets", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, format('{0}_files', parameters('ingestionContainerName')))]", + "approveStoragePrivateEndpointConnections": { + "condition": "[not(parameters('enablePublicAccess'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ApproveStoragePrivateEndpointConnections", "properties": { - "annotations": [], + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", "parameters": { - "folderPath": { - "type": "String" + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "privateEndpointConnections": { + "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" } }, - "type": "Parquet", - "typeProperties": { - "location": { - "type": "AzureBlobFSLocation", - "fileSystem": "[parameters('ingestionContainerName')]", - "folderPath": { - "value": "@dataset().folderPath", - "type": "Expression" + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "491732910990436410" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], + "metadata": { + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." + } + }, + "storageAccountName": { + "type": "string", + "metadata": { + "description": "Required. Name of the storage account." + } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", + "apiVersion": "2023-04-01", + "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline", + "actionRequired": "None" + } + } + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" } } - }, - "linkedServiceName": { - "parameters": {}, - "referenceName": "remoteHubStorage", - "type": "LinkedServiceReference" } }, "dependsOn": [ - "dataFactory::linkedService_remoteHubStorage" + "getStoragePrivateEndpointConnections" ] }, - "keyVault": { - "existing": true, - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]" - }, - "dataFactory": { - "existing": true, - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]" - }, - "appRegistration": { + "getKeyVaultPrivateEndpointConnections": { + "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.RemoteHub_Register", + "apiVersion": "2022-09-01", + "name": "GetKeyVaultPrivateEndpointConnections", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "version": { - "value": "[variables('finOpsToolkitVersion')]" - }, - "features": { - "value": [ - "DataFactory", - "KeyVault", - "Storage" - ] + "keyVaultName": { + "value": "[parameters('keyVaultName')]" } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "5436870138046688593" + "version": "0.36.177.2456", + "templateHash": "11127712826844297340" } }, - "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, + "keyVaultName": { + "type": "string", "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the KeyVault." } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" } } - }, - "HubAppFeature": { - "type": "string", - "allowedValues": [ - "DataFactory", - "KeyVault", - "Storage" - ], + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "keyVaultManagedPrivateEndpoint" + ] + }, + "approveKeyVaultPrivateEndpointConnections": { + "condition": "[and(not(empty(parameters('remoteHubStorageUri'))), not(parameters('enablePublicAccess')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ApproveKeyVaultPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "keyVaultName": { + "value": "[parameters('keyVaultName')]" + }, + "privateEndpointConnections": { + "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "11127712826844297340" + } + }, + "parameters": { + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], "metadata": { - "description": "FinOps hub app features.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, + "keyVaultName": { + "type": "string", "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } + "description": "Required. Name of the KeyVault." } } }, - "functions": [ + "resources": [ { - "namespace": "__bicep", - "members": { - "getAppPublisherTags": { - "parameters": [ - { - "$ref": "#/definitions/HubAppProperties", - "name": "app" - }, - { - "type": "string", - "name": "resourceType" - } - ], - "output": { - "type": "object", - "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" - }, - "metadata": { - "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", + "apiVersion": "2023-07-01", + "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" } } } ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getKeyVaultPrivateEndpointConnections" + ] + }, + "getDataExplorerPrivateEndpointConnections": { + "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "GetDataExplorerPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataExplorerName": { + "value": "[parameters('dataExplorerName')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9394304748737938982" + } + }, "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", + "privateEndpointConnections": { + "type": "array", + "defaultValue": [], "metadata": { - "description": "Required. FinOps hub app getting deployed." + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "version": { + "dataExplorerName": { "type": "string", "metadata": { - "description": "Required. Version number of the FinOps hub app." + "description": "Required. Name of the ADX cluster." } - }, - "features": { - "type": "array", - "items": { - "$ref": "#/definitions/HubAppFeature" + } + }, + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" }, - "defaultValue": [], - "metadata": { - "description": "Optional. Indicate which features the app requires. Allowed values: \"DataFactory\", \"KeyVault\", \"Storage\". Default: [] (none)." + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } } - }, - "storageRoles": { + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "dataExplorerManagedPrivateEndpoint" + ] + }, + "approveDataExplorerPrivateEndpointConnections": { + "condition": "[and(variables('deployDataExplorer'), not(parameters('enablePublicAccess')))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "ApproveDataExplorerPrivateEndpointConnections", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataExplorerName": { + "value": "[parameters('dataExplorerName')]" + }, + "privateEndpointConnections": { + "value": "[reference('getDataExplorerPrivateEndpointConnections').outputs.privateEndpointConnections.value]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "9394304748737938982" + } + }, + "parameters": { + "privateEndpointConnections": { "type": "array", - "items": { - "type": "string" - }, "defaultValue": [], "metadata": { - "description": "Optional. Indicate which RBAC roles the Data Factory identity needs on the storage account, if created. This is in addition to Storage Blob Data Contributor for reading and managing content. Default: [] (none)." + "description": "Optional. Array of private endpoint connections. Pending ones will be approved." } }, - "telemetryString": { + "dataExplorerName": { "type": "string", - "defaultValue": "", "metadata": { - "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + "description": "Required. Name of the ADX cluster." } } }, - "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", - "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", - "usesStorage": "[contains(parameters('features'), 'Storage')]", - "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', parameters('app').id, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", - "telemetryProps": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "[format('FTK: {0}', parameters('app').id)]", - "version": "[parameters('version')]" - } - }, - "resources": [] + "resources": [ + { + "copy": { + "name": "privateEndpointConnection", + "count": "[length(parameters('privateEndpointConnections'))]" + }, + "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", + "type": "Microsoft.Kusto/clusters/privateEndpointConnections", + "apiVersion": "2023-08-15", + "name": "[format('{0}/{1}', parameters('dataExplorerName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", + "properties": { + "privateLinkServiceConnectionState": { + "status": "Approved", + "description": "Approved-by-pipeline" + } } - }, - "autoStartRbacRoles": [ - "673868aa-7521-48a0-acc6-0f60742d39f5" - ], - "factoryStorageRoles": "[union(parameters('storageRoles'), createArray('17d1049b-9a84-46fb-8f53-869881c3d3ab', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe', 'acdd72a7-3385-48ef-bd42-f606fba81ae7'))]", - "storageInfrastructureEncryptionProperties": "[if(not(parameters('app').hub.options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('app').hub.options.storageInfrastructureEncryption)))]" + } + ], + "outputs": { + "privateEndpointConnections": { + "type": "array", + "value": "[reference(resourceId('Microsoft.Kusto/clusters', parameters('dataExplorerName')), '2023-08-15').privateEndpointConnections]" + } + } + } + }, + "dependsOn": [ + "getDataExplorerPrivateEndpointConnections" + ] + }, + "deleteOldResources": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_ADF.DeleteOldResources", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" }, - "resources": { - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').storage)]", - "properties": { - "name": "[parameters('app').storage]", - "groupId": "dfs", - "privateLinkResourceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "fqdns": [ - "[reference('storageAccount').primaryEndpoints.dfs]" - ] + "identityName": { + "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" + }, + "scriptContent": { + "value": "[variables('$fxv#0')]" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "storageAccount" - ] - }, - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint": { - "condition": "[and(and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks/managedPrivateEndpoints", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}/{2}', parameters('app').dataFactory, 'default', parameters('app').keyVault)]", - "properties": { - "name": "[parameters('app').keyVault]", - "groupId": "vault", - "privateLinkResourceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "fqdns": [ - "[reference('keyVault').vaultUri]" - ] + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork", - "keyVault" - ] - }, - "dataFactory::managedVirtualNetwork": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/managedVirtualNetworks", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'default')]", - "properties": {}, - "dependsOn": [ - "dataFactory" - ] - }, - "dataFactory::managedIntegrationRuntime": { - "condition": "[and(variables('usesDataFactory'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.DataFactory/factories/integrationRuntimes", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, 'ManagedIntegrationRuntime')]", + { + "name": "DataFactoryName", + "value": "[parameters('dataFactoryName')]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "588615643779078900" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", "properties": { - "type": "Managed", - "managedVirtualNetwork": { - "referenceName": "default", - "type": "ManagedVirtualNetworkReference" + "name": { + "type": "string" }, - "typeProperties": { - "computeProperties": { - "location": "[parameters('app').hub.location]", - "dataFlowProperties": { - "computeType": "General", - "coreCount": 8, - "timeToLive": 10, - "cleanup": false, - "customProperties": [] - }, - "copyComputeScaleProperties": { - "dataIntegrationUnit": 16, - "timeToLive": 30 - }, - "pipelineExternalComputeScaleProperties": { - "timeToLive": 30, - "numberOfPipelineNodes": 1, - "numberOfExternalNodes": 1 - } - } + "value": { + "type": "string" } - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedVirtualNetwork" - ] + } }, - "dataFactory::linkedService_keyVault": { - "condition": "[and(variables('usesDataFactory'), variables('usesKeyVault'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').keyVault)]", + "_1.HubProperties": { + "type": "object", "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureKeyVault", - "typeProperties": { - "baseUrl": "[reference(format('Microsoft.KeyVault/vaults/{0}', parameters('app').keyVault), '2023-02-01').vaultUri]" + "id": { + "type": "string" }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "keyVault" - ] - }, - "dataFactory::linkedService_storageAccount": { - "condition": "[and(variables('usesDataFactory'), variables('usesStorage'))]", - "type": "Microsoft.DataFactory/factories/linkedservices", - "apiVersion": "2018-06-01", - "name": "[format('{0}/{1}', parameters('app').dataFactory, parameters('app').storage)]", - "properties": { - "annotations": [], - "parameters": {}, - "type": "AzureBlobFS", - "typeProperties": { - "url": "[reference(format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage), '2021-08-01').primaryEndpoints.dfs]" + "name": { + "type": "string" }, - "connectVia": "[if(parameters('app').hub.options.privateRouting, createObject('referenceName', 'ManagedIntegrationRuntime', 'type', 'IntegrationRuntimeReference'), null())]" - }, - "dependsOn": [ - "dataFactory", - "dataFactory::managedIntegrationRuntime", - "storageAccount" - ] - }, - "storageAccount::blobService": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2022-09-01", - "name": "[format('{0}/{1}', parameters('app').storage, 'default')]", - "dependsOn": [ - "storageAccount" - ] - }, - "blobEndpoint::blobPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-blob-ep', parameters('app').storage), 'storage-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "blobEndpoint" - ] - }, - "dfsEndpoint::dfsPrivateDnsZoneGroup": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-dfs-ep', parameters('app').storage), 'dfs-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" - } - } - ] - }, - "dependsOn": [ - "dfsEndpoint" - ] - }, - "keyVault::keyVault_accessPolicies": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults/accessPolicies", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('app').keyVault, 'add')]", - "properties": { - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } - ] - }, - "dependsOn": [ - "dataFactory", - "keyVault" - ] - }, - "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2024-06-01", - "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", - "properties": { - "virtualNetwork": { - "id": "[parameters('app').hub.routing.networkId]" }, - "registrationEnabled": false - }, - "dependsOn": [ - "keyVaultPrivateDnsZone" - ] - }, - "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2023-11-01", - "name": "[format('{0}/{1}', format('{0}-ep', parameters('app').keyVault), 'keyvault-endpoint-zone')]", - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } } - ] - }, - "dependsOn": [ - "keyVaultEndpoint", - "keyVaultPrivateDnsZone" - ] - }, - "appTelemetry": { - "condition": "[parameters('app').hub.options.enableTelemetry]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2022-09-01", - "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Resources/deployments')]", - "properties": "[variables('telemetryProps')]" - }, - "dataFactory": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.DataFactory/factories", - "apiVersion": "2018-06-01", - "name": "[parameters('app').dataFactory]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.DataFactory/factories')]", - "identity": { - "type": "SystemAssigned" + } }, - "properties": { - "globalConfigurations": { - "PipelineBillingEnabled": "true" + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" } } }, - "storageRoleAssignments": { - "copy": { - "name": "storageRoleAssignments", - "count": "[length(variables('factoryStorageRoles'))]" - }, - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', parameters('app').storage)]", - "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage), variables('factoryStorageRoles')[copyIndex()], resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('factoryStorageRoles')[copyIndex()])]", - "principalId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "storageAccount" - ] - }, - "triggerManagerIdentity": { - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[format('{0}_triggerManager', parameters('app').dataFactory)]", - "location": "[parameters('app').hub.location]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "dependsOn": [ - "dataFactory" - ] - }, - "triggerManagerRoleAssignments": { - "copy": { - "name": "triggerManagerRoleAssignments", - "count": "[length(variables('autoStartRbacRoles'))]" - }, - "condition": "[variables('usesDataFactory')]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.DataFactory/factories/{0}', parameters('app').dataFactory)]", - "name": "[guid(resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory), variables('autoStartRbacRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}_triggerManager', parameters('app').dataFactory)))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('autoStartRbacRoles')[copyIndex()])]", - "principalId": "[reference('triggerManagerIdentity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "dataFactory", - "triggerManagerIdentity" - ] - }, - "storageAccount": { - "condition": "[variables('usesStorage')]", - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[parameters('app').storage]", - "location": "[parameters('app').hub.location]", - "sku": { - "name": "[parameters('app').hub.options.storageSku]" - }, - "kind": "BlockBlobStorage", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Storage/storageAccounts')]", - "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')))))]" - }, - "blobPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" - }, - "blobEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-blob-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "_1.HubRoutingProperties": { + "type": "object", "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" + "networkId": { + "type": "string" }, - "privateLinkServiceConnections": [ - { - "name": "blobLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "blob" - ] + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" } } - ] + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } }, - "dependsOn": [ - "storageAccount" - ] - }, - "dfsPrivateDnsZone": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "existing": true, - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "dfsEndpoint": { - "condition": "[and(variables('usesStorage'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-dfs-ep', parameters('app').storage)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", + "_1.IdNameObject": { + "type": "object", "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.storage]" + "id": { + "type": "string" }, - "privateLinkServiceConnections": [ - { - "name": "dfsLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]", - "groupIds": [ - "dfs" - ] - } - } - ] + "name": { + "type": "string" + } }, - "dependsOn": [ - "storageAccount" - ] + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "keyVault": { - "condition": "[variables('usesKeyVault')]", - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2023-02-01", - "name": "[parameters('app').keyVault]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.KeyVault/vaults')]", + "HubAppProperties": { + "type": "object", "properties": { - "sku": { - "name": "[parameters('app').hub.options.keyVaultSku]", - "family": "A" + "name": { + "type": "string" }, - "enabledForDeployment": true, - "enabledForTemplateDeployment": true, - "enabledForDiskEncryption": true, - "enableSoftDelete": true, - "softDeleteRetentionInDays": 90, - "enableRbacAuthorization": false, - "createMode": "default", - "tenantId": "[subscription().tenantId]", - "accessPolicies": [ - { - "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", - "tenantId": "[subscription().tenantId]", - "permissions": { - "secrets": [ - "get" - ] + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" } } - ], - "networkAcls": { - "bypass": "AzureServices", - "defaultAction": "[if(parameters('app').hub.options.privateRouting, 'Deny', 'Allow')]" + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" } }, - "dependsOn": [ - "dataFactory" - ] + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } }, - "keyVaultPrivateDnsZone": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2024-06-01", - "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", - "location": "global", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateDnsZones')]", - "properties": {} + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } }, - "keyVaultEndpoint": { - "condition": "[and(variables('usesKeyVault'), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Network/privateEndpoints", - "apiVersion": "2023-11-01", - "name": "[format('{0}-ep', parameters('app').keyVault)]", - "location": "[parameters('app').hub.location]", - "tags": "[__bicep.getAppPublisherTags(parameters('app'), 'Microsoft.Network/privateEndpoints')]", - "properties": { - "subnet": { - "id": "[parameters('app').hub.routing.subnets.keyVault]" - }, - "privateLinkServiceConnections": [ - { - "name": "keyVaultLink", - "properties": { - "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]", - "groupIds": [ - "vault" - ] - } - } - ] + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" }, - "dependsOn": [ - "keyVault" - ] + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" }, - "getKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetKeyVaultPrivateEndpointConnections", + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } - } + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" }, "dependsOn": [ - "dataFactory::managedVirtualNetwork::keyVaultManagedPrivateEndpoint", - "getStoragePrivateEndpointConnections", - "keyVault" + "identity" ] }, - "approveKeyVaultPrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesKeyVault')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveKeyVaultPrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "keyVaultName": { - "value": "[parameters('app').keyVault]" - }, - "privateEndpointConnections": { - "value": "[reference('getKeyVaultPrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8053086385192962823" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the KeyVault." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.KeyVault/vaults/privateEndpointConnections", - "apiVersion": "2023-07-01", - "name": "[format('{0}/{1}', parameters('keyVaultName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName')), '2023-07-01').privateEndpointConnections]" - } - } + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} } }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", "dependsOn": [ - "getKeyVaultPrivateEndpointConnections", - "keyVault" + "identity", + "identityRoleAssignments" ] - }, - "getStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "GetStoragePrivateEndpointConnections", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } - }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" - } - } + } + } + } + }, + "dependsOn": [ + "stopTriggers", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" + ] + }, + "stopTriggers": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_ADF.StopTriggers", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "app": { + "value": "[parameters('app')]" + }, + "identityName": { + "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" + }, + "scriptContent": { + "value": "[variables('$fxv#1')]" + }, + "arguments": { + "value": "-Stop" + }, + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('dataFactoryName')]" + }, + { + "name": "Triggers", + "value": "[join(variables('allHubTriggers'), '|')]" + } + ] + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "588615643779078900" + } + }, + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" } - }, - "dependsOn": [ - "dataFactory::managedVirtualNetwork::storageManagedPrivateEndpoint", - "stopTriggers", - "storageAccount" - ] + } }, - "approveStoragePrivateEndpointConnections": { - "condition": "[and(and(variables('usesDataFactory'), variables('usesStorage')), parameters('app').hub.options.privateRouting)]", - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "ApproveStoragePrivateEndpointConnections", + "_1.HubProperties": { + "type": "object", "properties": { - "expressionEvaluationOptions": { - "scope": "inner" + "id": { + "type": "string" }, - "mode": "Incremental", - "parameters": { - "storageAccountName": { - "value": "[parameters('app').storage]" - }, - "privateEndpointConnections": { - "value": "[reference('getStoragePrivateEndpointConnections').outputs.privateEndpointConnections.value]" - } + "name": { + "type": "string" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "17847147681312340960" - } - }, - "parameters": { - "privateEndpointConnections": { - "type": "array", - "defaultValue": [], - "metadata": { - "description": "Optional. Array of private endpoint connections. Pending ones will be approved." - } + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" }, - "storageAccountName": { - "type": "string", - "metadata": { - "description": "Required. Name of the storage account." - } - } - }, - "resources": [ - { - "copy": { - "name": "privateEndpointConnection", - "count": "[length(parameters('privateEndpointConnections'))]" - }, - "condition": "[equals(parameters('privateEndpointConnections')[copyIndex()].properties.privateLinkServiceConnectionState.status, 'Pending')]", - "type": "Microsoft.Storage/storageAccounts/privateEndpointConnections", - "apiVersion": "2023-04-01", - "name": "[format('{0}/{1}', parameters('storageAccountName'), last(array(split(parameters('privateEndpointConnections')[copyIndex()].id, '/'))))]", - "properties": { - "privateLinkServiceConnectionState": { - "status": "Approved", - "description": "Approved-by-pipeline", - "actionRequired": "None" - } - } - } - ], - "outputs": { - "privateEndpointConnections": { - "type": "array", - "value": "[reference(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2023-04-01').privateEndpointConnections]" + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" } } - } - }, - "dependsOn": [ - "getStoragePrivateEndpointConnections", - "storageAccount" - ] - }, - "stopTriggers": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[format('{0}.{1}_ADF.StopTriggers', parameters('app').publisher, parameters('app').name)]", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "arguments": { - "value": "-Stop" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[parameters('app').dataFactory]" - } - ] - } + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "queue": { + "$ref": "#/definitions/_1.IdNameObject" }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "table": { + "$ref": "#/definitions/_1.IdNameObject" } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } + "keyVault": { + "type": "string" }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } + "displayName": { + "type": "string" }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } + "suffix": { + "type": "string" }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } + "tags": { + "type": "object" } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } + }, + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } + }, + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } + }, + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } + }, + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } + }, + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" + }, + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" + }, + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } + } + }, + "dependsOn": [ + "triggerManagerIdentity", + "triggerManagerRoleAssignments" + ] + }, + "trigger_ExportManifestAdded": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_ExportManifestAddedTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('dataFactoryName')]" + }, + "triggerName": { + "value": "[variables('exportManifestAddedTriggerName')]" + }, + "pipelineName": { + "value": "[format('{0}_ExecuteETL', variables('safeExportContainerName'))]" + }, + "pipelineParameters": { + "value": { + "folderPath": "@triggerBody().folderPath", + "fileName": "@triggerBody().fileName" + } + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "storageContainer": { + "value": "[parameters('exportContainerName')]" + }, + "storagePathEndsWith": { + "value": "manifest.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "10717799137710795976" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." + } + }, + "triggerName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory trigger to create or update." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storageContainer": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storagePathStartsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." + } + }, + "storagePathEndsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } + }, + "pipelineName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } + }, + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + } + } + }, + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] + "parameters": "[parameters('pipelineParameters')]" + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] + } + } + } + ] + } + }, + "dependsOn": [ + "pipeline_ExecuteExportsETL", + "stopTriggers" + ] + }, + "trigger_IngestionManifestAdded": { + "condition": "[or(variables('deployDataExplorer'), variables('useFabric'))]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_IngestionManifestAddedTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('dataFactoryName')]" + }, + "triggerName": { + "value": "[variables('ingestionManifestAddedTriggerName')]" + }, + "pipelineName": { + "value": "[format('{0}_ExecuteETL', variables('safeIngestionContainerName'))]" + }, + "pipelineParameters": { + "value": { + "folderPath": "@triggerBody().folderPath" + } + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "storageContainer": { + "value": "[parameters('ingestionContainerName')]" + }, + "storagePathEndsWith": { + "value": "manifest.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "10717799137710795976" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." + } + }, + "triggerName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory trigger to create or update." + } + }, + "storageAccountName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storageContainer": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } + }, + "storagePathStartsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." + } + }, + "storagePathEndsWith": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } + }, + "pipelineName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } + }, + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + } + } + }, + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } - }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] - } + "parameters": "[parameters('pipelineParameters')]" } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] } - }, - "dependsOn": [ - "appTelemetry", - "dataFactory", - "triggerManagerIdentity", - "triggerManagerRoleAssignments" - ] + } } + ] + } + }, + "dependsOn": [ + "pipeline_ExecuteIngestionETL", + "stopTriggers" + ] + }, + "trigger_SettingsUpdated": { + "condition": "[parameters('enableManagedExports')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_SettingsUpdatedTrigger", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "dataFactoryName": { + "value": "[parameters('dataFactoryName')]" }, - "outputs": { - "dataFactoryId": { + "triggerName": { + "value": "[variables('updateConfigTriggerName')]" + }, + "pipelineName": { + "value": "[format('{0}_ConfigureExports', variables('safeConfigContainerName'))]" + }, + "pipelineParameters": { + "value": {} + }, + "storageAccountName": { + "value": "[parameters('storageAccountName')]" + }, + "storageContainer": { + "value": "[parameters('configContainerName')]" + }, + "storagePathEndsWith": { + "value": "settings.json" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "10717799137710795976" + } + }, + "parameters": { + "dataFactoryName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Data Factory instance." + } + }, + "triggerName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Data Factory trigger to create or update." + } + }, + "storageAccountName": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Resource ID of the Data Factory instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.DataFactory/factories', parameters('app').dataFactory)]" + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } }, - "keyVaultId": { + "storageContainer": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Resource ID of the Key Vault instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('app').keyVault)]" + "description": "Optional. Azure storage container to monitor for updates and trigger events for." + } }, - "storageAccountId": { + "storagePathStartsWith": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Resource ID of the storage account instance used by the FinOps hub app." - }, - "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('app').storage)]" + "description": "Optional. Beginning of the storage path within the specified storageContainer to monitor for updates and trigger events for." + } }, - "principalId": { + "storagePathEndsWith": { "type": "string", + "defaultValue": "", "metadata": { - "description": "Principal ID for the managed identity used by Data Factory." - }, - "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + "description": "Optional. End of the storage path to monitor for updates and trigger events for." + } }, - "triggerManagerIdentityName": { + "pipelineName": { "type": "string", "metadata": { - "description": "Name of the managed identity used to create and stop ADF triggers." - }, - "value": "[format('{0}_triggerManager', parameters('app').dataFactory)]" + "description": "Required. Name of the Data Factory pipeline to execute when the trigger is executed." + } + }, + "pipelineParameters": { + "type": "object", + "metadata": { + "description": "Required. Parameters to pass to the pipeline when the trigger is executed." + } } - } + }, + "resources": [ + { + "condition": "[not(empty(parameters('storageAccountName')))]", + "type": "Microsoft.DataFactory/factories/triggers", + "apiVersion": "2018-06-01", + "name": "[format('{0}/{1}', parameters('dataFactoryName'), parameters('triggerName'))]", + "properties": { + "annotations": [], + "pipelines": [ + { + "pipelineReference": { + "referenceName": "[parameters('pipelineName')]", + "type": "PipelineReference" + }, + "parameters": "[parameters('pipelineParameters')]" + } + ], + "type": "BlobEventsTrigger", + "typeProperties": { + "blobPathBeginsWith": "[format('/{0}/blobs/{1}', parameters('storageContainer'), parameters('storagePathStartsWith'))]", + "blobPathEndsWith": "[parameters('storagePathEndsWith')]", + "ignoreEmptyBlobs": true, + "scope": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]", + "events": [ + "Microsoft.Storage.BlobCreated" + ] + } + } + } + ] } - } + }, + "dependsOn": [ + "pipeline_ConfigureExports", + "stopTriggers" + ] }, - "keyVault_secret": { + "startTriggers": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "keyVault_secret", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.Core_ADF.StartTriggers", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "vaultName": { - "value": "[parameters('app').keyVault]" - }, - "secretName": { - "value": "[variables('storageKeySecretName')]" + "app": { + "value": "[parameters('app')]" }, - "secretValue": { - "value": "[parameters('remoteStorageKey')]" + "identityName": { + "value": "[format('{0}_triggerManager', parameters('dataFactoryName'))]" }, - "secretExpirationInSeconds": { - "value": 1702648632 + "scriptContent": { + "value": "[variables('$fxv#2')]" }, - "secretNotBeforeInSeconds": { - "value": 10000 + "environmentVariables": { + "value": [ + { + "name": "DataFactorySubscriptionId", + "value": "[subscription().id]" + }, + { + "name": "DataFactoryResourceGroup", + "value": "[resourceGroup().name]" + }, + { + "name": "DataFactoryName", + "value": "[parameters('dataFactoryName')]" + }, + { + "name": "Triggers", + "value": "[join(variables('allHubTriggers'), '|')]" + }, + { + "name": "Pipelines", + "value": "[join(createArray(format('{0}_InitializeHub', variables('safeConfigContainerName'))), '|')]" + } + ] } }, "template": { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "8808837526156050188" + "version": "0.36.177.2456", + "templateHash": "588615643779078900" } }, - "parameters": { - "vaultName": { - "type": "string", - "metadata": { - "description": "Required. Name of the publisher-specific Key Vault instance." + "definitions": { + "EnvironmentVariable": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } } }, - "secretName": { - "type": "string", + "_1.HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Required. Name of the Key Vault secret to create or update." + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "secretValue": { - "type": "securestring", + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, "metadata": { - "description": "Required. Value of the Key Vault secret." + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "secretExpirationInSeconds": { - "type": "int", - "defaultValue": -1, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, "metadata": { - "description": "Optional. Value of the Key Vault secret expiration date (exp) property. This is represented as seconds since Jan 1, 1970." + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } } }, - "secretNotBeforeInSeconds": { - "type": "int", - "defaultValue": -1, - "metadata": { - "description": "Optional. Value of the Key Vault secret not before date (nbf) property. This is represented as seconds since Jan 1, 1970." - } - } - }, - "resources": [ - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2023-02-01", - "name": "[format('{0}/{1}', parameters('vaultName'), parameters('secretName'))]", + "HubAppProperties": { + "type": "object", "properties": { - "attributes": "[union(createObject('enabled', true()), if(lessOrEquals(parameters('secretExpirationInSeconds'), 0), createObject(), createObject('exp', parameters('secretExpirationInSeconds'))), if(lessOrEquals(parameters('secretNotBeforeInSeconds'), 0), createObject(), createObject('nbf', parameters('secretNotBeforeInSeconds'))))]", - "value": "[parameters('secretValue')]" - } - } - ], - "outputs": { - "secretName": { - "type": "string", - "metadata": { - "description": "Name of the Key Vault secret." - }, - "value": "[parameters('secretName')]" - } - } - } - } - } - }, - "outputs": { - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Name of the Key Vault instance." - }, - "value": "[parameters('app').keyVault]" - } - } - } - }, - "dependsOn": [ - "core" - ] - }, - "deleteOldResources": { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.DeleteOldResources", - "properties": { - "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[reference('core').outputs.app.value]" - }, - "identityName": { - "value": "[reference('core').outputs.triggerManagerIdentityName.value]" - }, - "scriptContent": { - "value": "[variables('$fxv#1')]" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[reference('core').outputs.app.value.dataFactory]" - } - ] - } - }, - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "languageVersion": "2.0", - "contentVersion": "1.0.0.0", - "metadata": { - "_generator": { - "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" - } - }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.HubRoutingProperties": { - "type": "object", - "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" - }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/_1.HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + }, + "parameters": { + "app": { + "$ref": "#/definitions/HubAppProperties", + "metadata": { + "description": "Required. FinOps hub app the deployment script is being run for." + } }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" + "identityName": { + "type": "string", + "metadata": { + "description": "Required. Name of the managed identity to create." + } }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" + "scriptName": { + "type": "string", + "defaultValue": "[deployment().name]", + "metadata": { + "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + } }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" - } - } - }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" + "scriptContent": { + "type": "string", + "metadata": { + "description": "Required. Name of the deployment script to create." + } }, - "dataFactory": { - "type": "string" + "arguments": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Additional arguments to pass into the deployment script." + } }, - "keyVault": { - "type": "string" + "environmentVariables": { + "type": "array", + "items": { + "$ref": "#/definitions/EnvironmentVariable" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Environment variables to use for the deployment script." + } + } + }, + "variables": { + "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", + "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', format('{0}cg', parameters('app').hub.routing.scriptStorage), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" + }, + "resources": { + "identity": { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "apiVersion": "2023-01-31", + "name": "[parameters('identityName')]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", + "location": "[parameters('app').hub.location]" }, - "scripts": { - "type": "string" + "scriptStorageAccount": { + "condition": "[parameters('app').hub.options.privateRouting]", + "existing": true, + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" }, - "storage": { - "type": "string" - } - } - } - }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "_1.IdNameObject": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, - "HubAppProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" - }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } - }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - } - }, - "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to create." - } - }, - "scriptName": { - "type": "string", - "defaultValue": "[deployment().name]", - "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." - } - }, - "scriptContent": { - "type": "string", - "metadata": { - "description": "Required. Name of the deployment script to create." - } - }, - "arguments": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." - } - }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], - "metadata": { - "description": "Optional. Environment variables to use for the deployment script." - } - } - }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", - "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" + "identityRoleAssignments": { + "copy": { + "name": "identityRoleAssignments", + "count": "[length(variables('privateEndpointDeploymentRoles'))]" + }, + "condition": "[parameters('app').hub.options.privateRouting]", + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", + "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", + "principalId": "[reference('identity').principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "identity" + ] + }, + "script": { + "type": "Microsoft.Resources/deploymentScripts", + "apiVersion": "2023-08-01", + "name": "[parameters('scriptName')]", + "kind": "AzurePowerShell", + "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", + "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} + } + }, + "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '9.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", + "dependsOn": [ + "identity", + "identityRoleAssignments" + ] + } + } + } }, "dependsOn": [ - "identity" + "deleteOldResources", + "pipeline_InitializeHub", + "trigger_DailySchedule", + "trigger_ExportManifestAdded", + "trigger_IngestionManifestAdded", + "trigger_MonthlySchedule", + "trigger_SettingsUpdated", + "triggerManagerIdentity", + "triggerManagerRoleAssignments" ] + } + }, + "outputs": { + "resourceId": { + "type": "string", + "metadata": { + "description": "The Resource ID of the Data factory." + }, + "value": "[resourceId('Microsoft.DataFactory/factories', parameters('dataFactoryName'))]" }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } + "name": { + "type": "string", + "metadata": { + "description": "The Name of the Azure Data Factory instance." }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "value": "[parameters('dataFactoryName')]" } } } }, "dependsOn": [ - "core" + "cmExports", + "core", + "dataExplorer", + "remoteHub" ] }, - "startTriggers": { + "remoteHub": { + "condition": "[not(empty(parameters('remoteHubStorageKey')))]", "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "Microsoft.FinOpsHubs.StartTriggers", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.RemoteHub", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { - "app": { - "value": "[reference('core').outputs.app.value]" - }, - "dataFactoryInstances": { - "value": [ - "[reference('core').outputs.app.value.dataFactory]", - "[reference('cmExports').outputs.app.value.dataFactory]" - ] - }, - "identityName": { - "value": "[reference('core').outputs.triggerManagerIdentityName.value]" + "hub": { + "value": "[variables('hub')]" }, - "startAllTriggers": { - "value": true + "remoteStorageKey": { + "value": "[parameters('remoteHubStorageKey')]" } }, "template": { @@ -23950,97 +18064,11 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "4749940909471549408" + "version": "0.36.177.2456", + "templateHash": "3708155483370559900" } }, "definitions": { - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" - }, - "keyVaultSku": { - "type": "string" - }, - "networkAddressPrefix": { - "type": "string" - }, - "privateRouting": { - "type": "bool" - }, - "publisherIsolation": { - "type": "bool" - }, - "storageInfrastructureEncryption": { - "type": "bool" - }, - "storageSku": { - "type": "string" - } - } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" - } - } - } - }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." - }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } - }, "_1.HubRoutingProperties": { "type": "object", "properties": { @@ -24073,9 +18101,6 @@ "subnets": { "type": "object", "properties": { - "dataExplorer": { - "type": "string" - }, "dataFactory": { "type": "string" }, @@ -24102,7 +18127,6 @@ "table": "Resource ID and name for the table storage DNS zone." }, "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", "dataFactory": "Resource ID of the subnet for Data Factory instances.", "keyVault": "Resource ID of the subnet for Key Vault instances.", "scripts": "Resource ID of the subnet for deployment script storage.", @@ -24133,7 +18157,7 @@ } } }, - "HubAppProperties": { + "HubProperties": { "type": "object", "properties": { "id": { @@ -24142,39 +18166,78 @@ "name": { "type": "string" }, - "publisher": { - "type": "string" - }, - "suffix": { + "location": { "type": "string" }, "tags": { "type": "object" }, - "dataFactory": { - "type": "string" + "tagsByResource": { + "type": "object" }, - "keyVault": { + "version": { "type": "string" }, - "storage": { - "type": "string" + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } } }, "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", "__bicep_imported_from!": { "sourceTemplate": "hub-types.bicep" } @@ -24182,95 +18245,55 @@ } }, "parameters": { - "app": { - "$ref": "#/definitions/HubAppProperties", - "metadata": { - "description": "Required. FinOps hub app getting deployed." - } - }, - "dataFactoryInstances": { - "type": "array", - "items": { - "type": "string" - }, - "metadata": { - "description": "Required. List of Azure Data Factory instances to start triggers for. Can be up to 1 per publisher." - } - }, - "identityName": { - "type": "string", - "metadata": { - "description": "Required. Name of the managed identity to use when starting the triggers." - } - }, - "startAllTriggers": { - "type": "bool", - "defaultValue": false, + "hub": { + "$ref": "#/definitions/HubProperties", "metadata": { - "description": "Optional. Start all triggers for the Data Factory instances. Default: false." + "description": "Required. FinOps hub instance properties." } }, - "startPipelines": { - "type": "array", - "items": { - "type": "string" - }, - "defaultValue": [], + "remoteStorageKey": { + "type": "securestring", "metadata": { - "description": "Optional. List of pipelines to run. Default: [] (no pipelines)." + "description": "Required. Create and store a key for a remote storage account." } } }, "variables": { - "$fxv#0": "# Copyright (c) Microsoft Corporation.\n# Licensed under the MIT License.\n\nparam(\n [switch] $Stop\n)\n\n# Init outputs\n$DeploymentScriptOutputs = @{}\n\nif (-not $Stop)\n{\n Start-Sleep -Seconds 10\n}\n\n# Loop thru triggers\n$triggers = Get-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName\n\nWrite-Output \"Found $($triggers.Length) trigger(s)\"\n\nif ($startTriggers)\n{\n $triggers | ForEach-Object {\n $trigger = $_.Name\n if ($Stop)\n {\n Write-Output \"Stopping trigger $trigger...\"\n $triggerOutput = Stop-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force `\n -ErrorAction SilentlyContinue # Ignore errors, since the trigger may not exist\n }\n else\n {\n Write-Output \"Starting trigger $trigger...\"\n $triggerOutput = Start-AzDataFactoryV2Trigger `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -Name $trigger `\n -Force\n }\n if ($triggerOutput)\n {\n Write-Output \"done...\"\n }\n else\n {\n Write-Output \"failed...\"\n }\n $DeploymentScriptOutputs[$trigger] = $triggerOutput\n }\n\n if ($Stop)\n {\n Start-Sleep -Seconds 10\n }\n}\n\nif (-not [string]::IsNullOrWhiteSpace($env:Pipelines))\n{\n $env:Pipelines.Split('|') `\n | ForEach-Object {\n Write-Output \"Running the init pipeline...\"\n Invoke-AzDataFactoryV2Pipeline `\n -ResourceGroupName $env:DataFactoryResourceGroup `\n -DataFactoryName $env:DataFactoryName `\n -PipelineName $_\n }\n}\n", - "uniqueInstances": "[union(filter(parameters('dataFactoryInstances'), lambda('adf', not(empty(lambdaVariables('adf'))))), createArray())]" + "$fxv#0": "12.0" }, "resources": { - "initialize": { - "copy": { - "name": "initialize", - "count": "[length(variables('uniqueInstances'))]" - }, + "appRegistration": { "type": "Microsoft.Resources/deployments", - "apiVersion": "2025-04-01", - "name": "[if(lessOrEquals(length(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()])), 64), format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), substring(format('Microsoft.FinOpsHubs.Init_{0}', variables('uniqueInstances')[copyIndex()]), 0, 64))]", + "apiVersion": "2022-09-01", + "name": "Microsoft.FinOpsHubs.RemoteHub_Register", "properties": { "expressionEvaluationOptions": { - "scope": "inner" - }, - "mode": "Incremental", - "parameters": { - "app": { - "value": "[parameters('app')]" - }, - "identityName": { - "value": "[parameters('identityName')]" - }, - "scriptContent": { - "value": "[variables('$fxv#0')]" - }, - "environmentVariables": { - "value": [ - { - "name": "DataFactorySubscriptionId", - "value": "[subscription().id]" - }, - { - "name": "DataFactoryResourceGroup", - "value": "[resourceGroup().name]" - }, - { - "name": "DataFactoryName", - "value": "[variables('uniqueInstances')[copyIndex()]]" - }, - { - "name": "Pipelines", - "value": "[join(parameters('startPipelines'), '|')]" - }, - { - "name": "StartAllTriggers", - "value": "[string(parameters('startAllTriggers'))]" - } + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "hub": { + "value": "[parameters('hub')]" + }, + "publisher": { + "value": "Microsoft FinOps hubs" + }, + "namespace": { + "value": "Microsoft.FinOpsHubs" + }, + "appName": { + "value": "RemoteHub" + }, + "displayName": { + "value": "FinOps hub remote relay" + }, + "appVersion": { + "value": "[variables('$fxv#0')]" + }, + "features": { + "value": [ + "KeyVault", + "Storage" ] } }, @@ -24281,361 +18304,932 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.39.26.7824", - "templateHash": "2597700623393789941" + "version": "0.36.177.2456", + "templateHash": "15179190433979236138" + } + }, + "definitions": { + "_1.HubRoutingProperties": { + "type": "object", + "properties": { + "networkId": { + "type": "string" + }, + "networkName": { + "type": "string" + }, + "scriptStorage": { + "type": "string" + }, + "dnsZones": { + "type": "object", + "properties": { + "blob": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "dfs": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "queue": { + "$ref": "#/definitions/_1.IdNameObject" + }, + "table": { + "$ref": "#/definitions/_1.IdNameObject" + } + } + }, + "subnets": { + "type": "object", + "properties": { + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "scripts": { + "type": "string" + }, + "storage": { + "type": "string" + } + } + } + }, + "metadata": { + "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", + "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", + "scriptStorage": "Name of the storage account used for deployment scripts.", + "dnsZones": { + "blob": "Resource ID and name for the blob storage DNS zone.", + "dfs": "Resource ID and name for the DFS storage DNS zone.", + "queue": "Resource ID and name for the queue storage DNS zone.", + "table": "Resource ID and name for the table storage DNS zone." + }, + "subnets": { + "dataFactory": "Resource ID of the subnet for Data Factory instances.", + "keyVault": "Resource ID of the subnet for Key Vault instances.", + "scripts": "Resource ID of the subnet for deployment script storage.", + "storage": "Resource ID of the subnet for storage accounts." + }, + "description": "FinOps hub private network routing properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "_1.IdNameObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "metadata": { + "id": "Fully-qualified resource ID.", + "name": "Resource name.", + "description": "Resource ID and name.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppFeature": { + "type": "string", + "allowedValues": [ + "DataFactory", + "KeyVault", + "Storage" + ], + "metadata": { + "description": "FinOps hub app features.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubAppProperties": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "publisher": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "suffix": { + "type": "string" + }, + "tags": { + "type": "object" + } + } + }, + "hub": { + "$ref": "#/definitions/HubProperties" + }, + "dataFactory": { + "type": "string" + }, + "keyVault": { + "type": "string" + }, + "storage": { + "type": "string" + } + }, + "metadata": { + "name": "Short name of the FinOps hub app (not including the publisher namespace).", + "displayName": "Display name of the FinOps hub app.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app.", + "publisher": { + "name": "Fully-qualified namespace of the FinOps hub app publisher.", + "displayName": "Display name of the FinOps hub app publisher.", + "suffix": "Unique suffix used for publisher resources.", + "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher." + }, + "hub": "FinOps hub instance the app is deployed to.", + "dataFactory": "Name of the Data Factory instance for this publisher.", + "keyVault": "Name of the KeyVault instance for this publisher.", + "storage": "Name of the storage account for this publisher.", + "description": "FinOps hub app configuration settings.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "HubProperties": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "tags": { + "type": "object" + }, + "tagsByResource": { + "type": "object" + }, + "version": { + "type": "string" + }, + "options": { + "type": "object", + "properties": { + "enableTelemetry": { + "type": "bool" + }, + "keyVaultSku": { + "type": "string" + }, + "networkAddressPrefix": { + "type": "string" + }, + "privateRouting": { + "type": "bool" + }, + "publisherIsolation": { + "type": "bool" + }, + "storageInfrastructureEncryption": { + "type": "bool" + }, + "storageSku": { + "type": "string" + } + } + }, + "routing": { + "$ref": "#/definitions/_1.HubRoutingProperties" + }, + "core": { + "type": "object", + "properties": { + "suffix": { + "type": "string" + } + } + } + }, + "metadata": { + "id": "FinOps hub resource ID.", + "name": "FinOps hub instance name.", + "location": "Azure resource location of the FinOps hub instance.", + "tags": "Tags to apply to all FinOps hub resources.", + "tagsByResource": "Tags to apply to resources based on their resource type.", + "version": "FinOps hub version number.", + "options": { + "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", + "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", + "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", + "privateRouting": "Indicates whether private network routing is enabled.", + "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", + "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", + "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." + }, + "routing": "FinOps hub private network routing properties, if enabled.", + "core": { + "suffix": "Unique suffix used for shared resources." + }, + "apps": {}, + "description": "FinOps hub instance properties.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } }, - "definitions": { - "EnvironmentVariable": { - "type": "object", - "properties": { - "name": { - "type": "string" + "functions": [ + { + "namespace": "__bicep", + "members": { + "getAppTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + }, + { + "type": "bool", + "nullable": true, + "name": "forceAppTags" + } + ], + "output": { + "type": "object", + "value": "[union(if(or(parameters('app').hub.options.publisherIsolation, coalesce(parameters('forceAppTags'), false())), parameters('app').tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } }, - "value": { - "type": "string" + "getPublisherTags": { + "parameters": [ + { + "$ref": "#/definitions/HubAppProperties", + "name": "app" + }, + { + "type": "string", + "name": "resourceType" + } + ], + "output": { + "type": "object", + "value": "[union(if(parameters('app').hub.options.publisherIsolation, parameters('app').publisher.tags, parameters('app').hub.tags), coalesce(tryGet(parameters('app').hub.tagsByResource, parameters('resourceType')), createObject()))]" + }, + "metadata": { + "description": "Returns a tags dictionary that includes tags for the FinOps hub app publisher.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "newApp": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" + }, + { + "type": "string", + "name": "publisherDisplayName" + }, + { + "type": "string", + "name": "publisherName" + }, + { + "type": "string", + "name": "appPartialName" + }, + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" + } + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": "[_1.newAppInternal(parameters('hub'), parameters('publisherName'), parameters('publisherDisplayName'), if(or(not(parameters('hub').options.publisherIsolation), equals(parameters('publisherName'), 'Microsoft.FinOpsHubs')), parameters('hub').core.suffix, uniqueString(parameters('publisherName'))), createObject('ftk-hubapp-publisher', parameters('publisherName')), format('{0}.{1}', parameters('publisherName'), parameters('appPartialName')), parameters('appDisplayName'), parameters('version'))]" + }, + "metadata": { + "description": "Creates a new FinOps hub app configuration object.", + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } } } }, - "_1.HubProperties": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "location": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "tagsByResource": { - "type": "object" - }, - "version": { - "type": "string" - }, - "options": { - "type": "object", - "properties": { - "enableTelemetry": { - "type": "bool" + { + "namespace": "_1", + "members": { + "newAppInternal": { + "parameters": [ + { + "$ref": "#/definitions/HubProperties", + "name": "hub" }, - "keyVaultSku": { - "type": "string" + { + "type": "string", + "name": "publisherName" }, - "networkAddressPrefix": { - "type": "string" + { + "type": "string", + "name": "publisherDisplayName" }, - "privateRouting": { - "type": "bool" + { + "type": "string", + "name": "publisherSuffix" }, - "publisherIsolation": { - "type": "bool" + { + "type": "object", + "name": "publisherTags" }, - "storageInfrastructureEncryption": { - "type": "bool" + { + "type": "string", + "name": "appName" }, - "storageSku": { - "type": "string" + { + "type": "string", + "name": "appDisplayName" + }, + { + "type": "string", + "name": "version" + } + ], + "output": { + "$ref": "#/definitions/HubAppProperties", + "value": { + "name": "[parameters('appName')]", + "displayName": "[parameters('appDisplayName')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'), createObject('ftk-hubapp', parameters('appName'), 'ftk-hubapp-version', parameters('version')))]", + "publisher": { + "name": "[parameters('publisherName')]", + "displayName": "[parameters('publisherDisplayName')]", + "suffix": "[parameters('publisherSuffix')]", + "tags": "[union(parameters('hub').tags, parameters('publisherTags'))]" + }, + "hub": "[parameters('hub')]", + "dataFactory": "[replace(format('{0}-{1}', take(format('{0}-engine', replace(parameters('hub').name, '_', '-')), sub(sub(63, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "keyVault": "[replace(format('{0}-{1}', take(format('{0}-vault', replace(parameters('hub').name, '_', '-')), sub(sub(24, length(parameters('publisherSuffix'))), 1)), parameters('publisherSuffix')), '--', '-')]", + "storage": "[format('{0}{1}', take(_1.safeStorageName(parameters('hub').name), sub(24, length(parameters('publisherSuffix')))), parameters('publisherSuffix'))]" + } + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + }, + "safeStorageName": { + "parameters": [ + { + "type": "string", + "name": "name" + } + ], + "output": { + "type": "string", + "value": "[replace(replace(toLower(parameters('name')), '-', ''), '_', '')]" + }, + "metadata": { + "__bicep_imported_from!": { + "sourceTemplate": "hub-types.bicep" + } + } + } + } + } + ], + "parameters": { + "hub": { + "$ref": "#/definitions/HubProperties", + "metadata": { + "description": "Required. FinOps hub instance properties." + } + }, + "publisher": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app publisher." + } + }, + "namespace": { + "type": "string", + "metadata": { + "description": "Required. Namespace to use for the FinOps hub app publisher. Will be combined with appName to form a fully-qualified identifier. Must be an alphanumeric string without spaces or special characters except for periods. This value should never change and will be used to uniquely identify the publisher. A change would require migrating content to the new publisher. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "appName": { + "type": "string", + "metadata": { + "description": "Required. Unique identifier of the FinOps hub app within the publisher namespace. Must be an alphanumeric string without spaces or special characters. This name should never change and will be used with the namespace to fully qualify the app. A change would require migrating content to the new app. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + }, + "displayName": { + "type": "string", + "metadata": { + "description": "Required. Display name of the FinOps hub app." + } + }, + "appVersion": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Version number of the FinOps hub app." + } + }, + "features": { + "type": "array", + "items": { + "$ref": "#/definitions/HubAppFeature" + }, + "defaultValue": [], + "metadata": { + "description": "Optional. Indicate which features the app requires. Allowed values: \"Storage\". Default: [] (none)." + } + }, + "telemetryString": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Optional. Custom string with additional metadata to log. Must an alphanumeric string without spaces or special characters except for underscores and dashes. Namespace + appName + telemetryString must be 50 characters or less - additional characters will be trimmed." + } + } + }, + "variables": { + "app": "[__bicep.newApp(parameters('hub'), parameters('publisher'), parameters('namespace'), parameters('appName'), parameters('displayName'), parameters('appVersion'))]", + "usesDataFactory": "[contains(parameters('features'), 'DataFactory')]", + "usesKeyVault": "[contains(parameters('features'), 'KeyVault')]", + "usesStorage": "[contains(parameters('features'), 'Storage')]", + "telemetryId": "[format('ftk-hubapp-{0}{1}{2}', variables('app').name, if(empty(parameters('telemetryString')), '', '_'), parameters('telemetryString'))]", + "telemetryProps": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "[format('FTK: {0} - {1} {2}', parameters('publisher'), parameters('displayName'), variables('telemetryId'))]", + "version": "[parameters('appVersion')]" + } + }, + "resources": [] + } + }, + "storageInfrastructureEncryptionProperties": "[if(not(parameters('hub').options.storageInfrastructureEncryption), createObject(), createObject('encryption', createObject('keySource', 'Microsoft.Storage', 'requireInfrastructureEncryption', parameters('hub').options.storageInfrastructureEncryption)))]" + }, + "resources": { + "storageAccount::blobService": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts/blobServices", + "apiVersion": "2022-09-01", + "name": "[format('{0}/{1}', variables('app').storage, 'default')]", + "dependsOn": [ + "storageAccount" + ] + }, + "blobEndpoint::blobPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-blob-ep', variables('app').storage), 'storage-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.blob.{0}', environment().suffixes.storage))]" + } + } + ] + }, + "dependsOn": [ + "blobEndpoint" + ] + }, + "dfsEndpoint::dfsPrivateDnsZoneGroup": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-dfs-ep', variables('app').storage), 'dfs-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink.dfs.{0}', environment().suffixes.storage))]" } } - }, - "routing": { - "$ref": "#/definitions/_1.HubRoutingProperties" - }, - "core": { - "type": "object", - "properties": { - "suffix": { - "type": "string" + ] + }, + "dependsOn": [ + "dfsEndpoint" + ] + }, + "keyVault::keyVault_accessPolicies": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults/accessPolicies", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', variables('app').keyVault, 'add')]", + "properties": { + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] } } - } + ] }, - "metadata": { - "id": "FinOps hub resource ID.", - "name": "FinOps hub instance name.", - "location": "Azure resource location of the FinOps hub instance.", - "tags": "Tags to apply to all FinOps hub resources.", - "tagsByResource": "Tags to apply to resources based on their resource type.", - "version": "FinOps hub version number.", - "options": { - "enableTelemetry": "Indicates whether telemetry should be enabled for deployments.", - "keyVaultSku": "KeyVault SKU. Allowed values: \"standard\", \"premium\".", - "networkAddressPrefix": "Address prefix for the FinOps hub isolated virtual network, if private network routing is enabled.", - "privateRouting": "Indicates whether private network routing is enabled.", - "publisherIsolation": "Indicates whether FinOps hub resources should be separated by publisher for advanced security.", - "storageInfrastructureEncryption": "Indicates whether infrastructure encryption is enabled for the storage account.", - "storageSku": "Storage account SKU. Allowed values: \"Premium_LRS\", \"Premium_ZRS\"." - }, - "routing": "FinOps hub private network routing properties, if enabled.", - "core": { - "suffix": "Unique suffix used for shared resources." + "dependsOn": [ + "dataFactory", + "keyVault" + ] + }, + "keyVaultPrivateDnsZone::keyVaultPrivateDnsZoneLink": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2024-06-01", + "name": "[format('{0}/{1}', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), format('{0}-link', replace(format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')), '.', '-')))]", + "location": "global", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones/virtualNetworkLinks')]", + "properties": { + "virtualNetwork": { + "id": "[parameters('hub').routing.networkId]" }, - "apps": {}, - "description": "FinOps hub instance properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" + "registrationEnabled": false + }, + "dependsOn": [ + "keyVaultPrivateDnsZone" + ] + }, + "keyVaultEndpoint::keyVaultPrivateDnsZoneGroup": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2023-11-01", + "name": "[format('{0}/{1}', format('{0}-ep', variables('app').keyVault), 'keyvault-endpoint-zone')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore')))]" + } + } + ] + }, + "dependsOn": [ + "keyVaultEndpoint", + "keyVaultPrivateDnsZone" + ] + }, + "appTelemetry": { + "condition": "[parameters('hub').options.enableTelemetry]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "[if(lessOrEquals(length(variables('telemetryId')), 64), variables('telemetryId'), substring(variables('telemetryId'), 0, 64))]", + "tags": "[__bicep.getAppTags(variables('app'), 'Microsoft.Resources/deployments', true())]", + "properties": "[variables('telemetryProps')]" + }, + "dataFactory": { + "condition": "[variables('usesDataFactory')]", + "type": "Microsoft.DataFactory/factories", + "apiVersion": "2018-06-01", + "name": "[variables('app').dataFactory]", + "location": "[variables('app').hub.location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.DataFactory/factories')]", + "identity": { + "type": "SystemAssigned" + }, + "properties": { + "globalConfigurations": { + "PipelineBillingEnabled": "true" } } }, - "_1.HubRoutingProperties": { - "type": "object", + "storageAccount": { + "condition": "[variables('usesStorage')]", + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2022-09-01", + "name": "[variables('app').storage]", + "location": "[parameters('hub').location]", + "sku": { + "name": "[parameters('hub').options.storageSku]" + }, + "kind": "BlockBlobStorage", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Storage/storageAccounts')]", + "properties": "[shallowMerge(createArray(variables('storageInfrastructureEncryptionProperties'), createObject('supportsHttpsTrafficOnly', true(), 'allowSharedKeyAccess', true(), 'isHnsEnabled', true(), 'minimumTlsVersion', 'TLS1_2', 'allowBlobPublicAccess', false(), 'publicNetworkAccess', 'Enabled', 'networkAcls', createObject('bypass', 'AzureServices', 'defaultAction', if(parameters('hub').options.privateRouting, 'Deny', 'Allow')))))]" + }, + "blobPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.blob.{0}', environment().suffixes.storage)]" + }, + "blobEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-blob-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { - "networkId": { - "type": "string" - }, - "networkName": { - "type": "string" - }, - "scriptStorage": { - "type": "string" + "subnet": { + "id": "[parameters('hub').routing.subnets.storage]" }, - "dnsZones": { - "type": "object", - "properties": { - "blob": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "dfs": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "queue": { - "$ref": "#/definitions/_1.IdNameObject" - }, - "table": { - "$ref": "#/definitions/_1.IdNameObject" + "privateLinkServiceConnections": [ + { + "name": "blobLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", + "groupIds": [ + "blob" + ] } } + ] + }, + "dependsOn": [ + "storageAccount" + ] + }, + "dfsPrivateDnsZone": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "existing": true, + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink.dfs.{0}', environment().suffixes.storage)]" + }, + "dfsEndpoint": { + "condition": "[and(variables('usesStorage'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-dfs-ep', variables('app').storage)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", + "properties": { + "subnet": { + "id": "[parameters('hub').routing.subnets.storage]" }, - "subnets": { - "type": "object", - "properties": { - "dataExplorer": { - "type": "string" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "scripts": { - "type": "string" - }, - "storage": { - "type": "string" + "privateLinkServiceConnections": [ + { + "name": "dfsLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', variables('app').storage)]", + "groupIds": [ + "dfs" + ] } } - } + ] }, - "metadata": { - "networkId": "Resource ID of the FinOps hub isolated virtual network, if private network routing is enabled.", - "networkName": "Name of the FinOps hub isolated virtual network, if private network routing is enabled.", - "scriptStorage": "Name of the storage account used for deployment scripts.", - "dnsZones": { - "blob": "Resource ID and name for the blob storage DNS zone.", - "dfs": "Resource ID and name for the DFS storage DNS zone.", - "queue": "Resource ID and name for the queue storage DNS zone.", - "table": "Resource ID and name for the table storage DNS zone." - }, - "subnets": { - "dataExplorer": "Resource ID of the subnet for the Data Explorer instance.", - "dataFactory": "Resource ID of the subnet for Data Factory instances.", - "keyVault": "Resource ID of the subnet for Key Vault instances.", - "scripts": "Resource ID of the subnet for deployment script storage.", - "storage": "Resource ID of the subnet for storage accounts." - }, - "description": "FinOps hub private network routing properties.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dependsOn": [ + "storageAccount" + ] }, - "_1.IdNameObject": { - "type": "object", + "keyVault": { + "condition": "[variables('usesKeyVault')]", + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2023-02-01", + "name": "[variables('app').keyVault]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.KeyVault/vaults')]", "properties": { - "id": { - "type": "string" + "sku": { + "name": "[parameters('hub').options.keyVaultSku]", + "family": "A" }, - "name": { - "type": "string" + "enabledForDeployment": true, + "enabledForTemplateDeployment": true, + "enabledForDiskEncryption": true, + "enableSoftDelete": true, + "softDeleteRetentionInDays": 90, + "enableRbacAuthorization": false, + "createMode": "default", + "tenantId": "[subscription().tenantId]", + "accessPolicies": [ + { + "objectId": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]", + "tenantId": "[subscription().tenantId]", + "permissions": { + "secrets": [ + "get" + ] + } + } + ], + "networkAcls": { + "bypass": "AzureServices", + "defaultAction": "[if(parameters('hub').options.privateRouting, 'Deny', 'Allow')]" } }, - "metadata": { - "id": "Fully-qualified resource ID.", - "name": "Resource name.", - "description": "Resource ID and name.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dependsOn": [ + "dataFactory" + ] }, - "HubAppProperties": { - "type": "object", + "keyVaultPrivateDnsZone": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2024-06-01", + "name": "[format('privatelink{0}', replace(environment().suffixes.keyvaultDns, 'vault', 'vaultcore'))]", + "location": "global", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateDnsZones')]", + "properties": {} + }, + "keyVaultEndpoint": { + "condition": "[and(variables('usesKeyVault'), parameters('hub').options.privateRouting)]", + "type": "Microsoft.Network/privateEndpoints", + "apiVersion": "2023-11-01", + "name": "[format('{0}-ep', variables('app').keyVault)]", + "location": "[parameters('hub').location]", + "tags": "[__bicep.getPublisherTags(variables('app'), 'Microsoft.Network/privateEndpoints')]", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "publisher": { - "type": "string" - }, - "suffix": { - "type": "string" - }, - "tags": { - "type": "object" - }, - "dataFactory": { - "type": "string" - }, - "keyVault": { - "type": "string" - }, - "storage": { - "type": "string" + "subnet": { + "id": "[parameters('hub').routing.subnets.keyVault]" }, - "hub": { - "$ref": "#/definitions/_1.HubProperties" - } + "privateLinkServiceConnections": [ + { + "name": "keyVaultLink", + "properties": { + "privateLinkServiceId": "[resourceId('Microsoft.KeyVault/vaults', variables('app').keyVault)]", + "groupIds": [ + "vault" + ] + } + } + ] }, - "metadata": { - "id": "Fully-qualified name of the publisher and app, separated by a dot.", - "name": "Short name of the FinOps hub app. Last segment of the app ID.", - "publisher": "Fully-qualified namespace of the FinOps hub app publisher.", - "suffix": "Unique suffix used for publisher resources.", - "tags": "Tags to apply to all FinOps hub resources for this FinOps hub app publisher. Tags are not specific to the app since resources are shared.", - "dataFactory": "Name of the Data Factory instance for this publisher.", - "keyVault": "Name of the KeyVault instance for this publisher.", - "storage": "Name of the storage account for this publisher.", - "hub": "FinOps hub instance the app is deployed to.", - "description": "FinOps hub app configuration settings.", - "__bicep_imported_from!": { - "sourceTemplate": "hub-types.bicep" - } - } + "dependsOn": [ + "keyVault" + ] } }, - "parameters": { + "outputs": { "app": { "$ref": "#/definitions/HubAppProperties", "metadata": { - "description": "Required. FinOps hub app the deployment script is being run for." - } + "description": "FinOps hub app configuration." + }, + "value": "[variables('app')]" }, - "identityName": { + "principalId": { "type": "string", "metadata": { - "description": "Required. Name of the managed identity to create." + "description": "Principal ID for the managed identity used by Data Factory." + }, + "value": "[reference('dataFactory', '2018-06-01', 'full').identity.principalId]" + } + } + } + } + }, + "keyVault_secret": { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2022-09-01", + "name": "keyVault_secret", + "properties": { + "expressionEvaluationOptions": { + "scope": "inner" + }, + "mode": "Incremental", + "parameters": { + "vaultName": { + "value": "[reference('appRegistration').outputs.app.value.keyVault]" + }, + "secretName": { + "value": "[format('{0}-storage-key', toLower(reference('appRegistration').outputs.app.value.hub.name))]" + }, + "secretValue": { + "value": "[parameters('remoteStorageKey')]" + }, + "secretExpirationInSeconds": { + "value": 1702648632 + }, + "secretNotBeforeInSeconds": { + "value": 10000 + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.36.177.2456", + "templateHash": "338893459125049689" + } + }, + "parameters": { + "vaultName": { + "type": "string", + "metadata": { + "description": "Required. Name of the publisher-specific Key Vault instance." } }, - "scriptName": { + "secretName": { "type": "string", - "defaultValue": "[deployment().name]", "metadata": { - "description": "Optional. Name of the deployment script to create. Default = (same as deployment)." + "description": "Required. Name of the Key Vault secret to create or update." } }, - "scriptContent": { - "type": "string", + "secretValue": { + "type": "securestring", "metadata": { - "description": "Required. Name of the deployment script to create." + "description": "Required. Value of the Key Vault secret." } }, - "arguments": { - "type": "string", - "defaultValue": "", + "secretExpirationInSeconds": { + "type": "int", + "defaultValue": -1, "metadata": { - "description": "Optional. Additional arguments to pass into the deployment script." + "description": "Optional. Value of the Key Vault secret expiration date (exp) property. This is represented as seconds since Jan 1, 1970." } }, - "environmentVariables": { - "type": "array", - "items": { - "$ref": "#/definitions/EnvironmentVariable" - }, - "defaultValue": [], + "secretNotBeforeInSeconds": { + "type": "int", + "defaultValue": -1, "metadata": { - "description": "Optional. Environment variables to use for the deployment script." + "description": "Optional. Value of the Key Vault secret not before date (nbf) property. This is represented as seconds since Jan 1, 1970." } } }, - "variables": { - "privateEndpointDeploymentRoles": "[if(not(parameters('app').hub.options.privateRouting), createArray(), createArray('69566ab7-960f-475b-8e7c-b3118f30c6bd'))]", - "containerGroupName": "[replace(replace(replace(parameters('scriptName'), '/', '-'), '.', '-'), '_', '-')]", - "privateEndpointDeploymentProperties": "[if(not(parameters('app').hub.options.privateRouting), createObject(), createObject('storageAccountSettings', createObject('storageAccountName', coalesce(parameters('app').hub.routing.scriptStorage, '')), 'containerSettings', createObject('containerGroupName', if(greater(length(variables('containerGroupName')), 63), substring(variables('containerGroupName'), 0, 62), variables('containerGroupName')), 'subnetIds', createArray(createObject('id', coalesce(parameters('app').hub.routing.subnets.scripts, ''))))))]" - }, - "resources": { - "identity": { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "apiVersion": "2023-01-31", - "name": "[parameters('identityName')]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.ManagedIdentity/userAssignedIdentities'), createObject()))]", - "location": "[parameters('app').hub.location]" - }, - "scriptStorageAccount": { - "condition": "[parameters('app').hub.options.privateRouting]", - "existing": true, - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2022-09-01", - "name": "[coalesce(parameters('app').hub.routing.scriptStorage, '')]" - }, - "identityRoleAssignments": { - "copy": { - "name": "identityRoleAssignments", - "count": "[length(variables('privateEndpointDeploymentRoles'))]" - }, - "condition": "[parameters('app').hub.options.privateRouting]", - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2022-04-01", - "scope": "[format('Microsoft.Storage/storageAccounts/{0}', coalesce(parameters('app').hub.routing.scriptStorage, ''))]", - "name": "[guid(variables('privateEndpointDeploymentRoles')[copyIndex()], resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]", + "resources": [ + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2023-02-01", + "name": "[format('{0}/{1}', parameters('vaultName'), parameters('secretName'))]", "properties": { - "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', variables('privateEndpointDeploymentRoles')[copyIndex()])]", - "principalId": "[reference('identity').principalId]", - "principalType": "ServicePrincipal" - }, - "dependsOn": [ - "identity" - ] - }, - "script": { - "type": "Microsoft.Resources/deploymentScripts", - "apiVersion": "2023-08-01", - "name": "[parameters('scriptName')]", - "kind": "AzurePowerShell", - "location": "[if(startsWith(parameters('app').hub.location, 'china'), 'chinaeast2', parameters('app').hub.location)]", - "tags": "[union(parameters('app').tags, coalesce(tryGet(parameters('app').hub.tagsByResource, 'Microsoft.Resources/deploymentScripts'), createObject()))]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', parameters('identityName')))]": {} - } + "attributes": "[union(createObject('enabled', true()), if(lessOrEquals(parameters('secretExpirationInSeconds'), 0), createObject(), createObject('exp', parameters('secretExpirationInSeconds'))), if(lessOrEquals(parameters('secretNotBeforeInSeconds'), 0), createObject(), createObject('nbf', parameters('secretNotBeforeInSeconds'))))]", + "value": "[parameters('secretValue')]" + } + } + ], + "outputs": { + "secretName": { + "type": "string", + "metadata": { + "description": "Name of the Key Vault secret." }, - "properties": "[shallowMerge(createArray(variables('privateEndpointDeploymentProperties'), createObject('azPowerShellVersion', '11.0', 'retentionInterval', 'PT1H', 'cleanupPreference', 'OnSuccess', 'scriptContent', parameters('scriptContent'), 'arguments', parameters('arguments'), 'environmentVariables', parameters('environmentVariables'))))]", - "dependsOn": [ - "identity", - "identityRoleAssignments" - ] + "value": "[parameters('secretName')]" } } } - } + }, + "dependsOn": [ + "appRegistration" + ] + } + }, + "outputs": { + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Name of the Key Vault instance." + }, + "value": "[reference('appRegistration').outputs.app.value.keyVault]" } } } - }, - "dependsOn": [ - "cmExports", - "core" - ] + } } }, "outputs": { @@ -24686,28 +19280,28 @@ "metadata": { "description": "The resource ID of the Data Explorer cluster." }, - "value": "[if(not(variables('useAzureDataExplorer')), '', reference('analytics').outputs.clusterId.value)]" + "value": "[if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.clusterId.value)]" }, "clusterUri": { "type": "string", "metadata": { "description": "The URI of the Data Explorer cluster." }, - "value": "[if(variables('useFabric'), parameters('fabricQueryUri'), if(not(variables('useAzureDataExplorer')), '', reference('analytics').outputs.clusterUri.value))]" + "value": "[if(variables('useFabric'), parameters('fabricQueryUri'), if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.clusterUri.value))]" }, "ingestionDbName": { "type": "string", "metadata": { "description": "The name of the Data Explorer database used for ingesting data." }, - "value": "[if(or(variables('useFabric'), variables('useAzureDataExplorer')), reference('analytics').outputs.ingestionDbName.value, '')]" + "value": "[if(variables('useFabric'), 'Ingestion', if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.ingestionDbName.value))]" }, "hubDbName": { "type": "string", "metadata": { "description": "The name of the Data Explorer database used for querying data." }, - "value": "[if(or(variables('useFabric'), variables('useAzureDataExplorer')), reference('analytics').outputs.hubDbName.value, '')]" + "value": "[if(variables('useFabric'), 'Hub', if(not(variables('deployDataExplorer')), '', reference('dataExplorer').outputs.hubDbName.value))]" }, "managedIdentityId": { "type": "string", @@ -24748,70 +19342,70 @@ "metadata": { "description": "Name of the Data Factory instance." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.dataFactoryName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.dataFactoryName.value]" }, "storageAccountId": { "type": "string", "metadata": { "description": "Resource ID of the deployed storage account." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageAccountId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageAccountId.value]" }, "storageAccountName": { "type": "string", "metadata": { "description": "Name of the storage account created for the hub instance. This must be used when connecting FinOps toolkit Power BI reports to your data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageAccountName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageAccountName.value]" }, "storageUrlForPowerBI": { "type": "string", "metadata": { "description": "URL to use when connecting custom Power BI reports to your data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.storageUrlForPowerBI.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.storageUrlForPowerBI.value]" }, "clusterId": { "type": "string", "metadata": { "description": "Resource ID of the Data Explorer cluster." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.clusterId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.clusterId.value]" }, "clusterUri": { "type": "string", "metadata": { "description": "URI of the Data Explorer cluster." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.clusterUri.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.clusterUri.value]" }, "ingestionDbName": { "type": "string", "metadata": { "description": "Name of the Data Explorer database used for ingesting data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.ingestionDbName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.ingestionDbName.value]" }, "hubDbName": { "type": "string", "metadata": { "description": "Name of the Data Explorer database used for querying data." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.hubDbName.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.hubDbName.value]" }, "managedIdentityId": { "type": "string", "metadata": { "description": "Object ID of the Data Factory managed identity. This will be needed when configuring managed exports." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.managedIdentityId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.managedIdentityId.value]" }, "managedIdentityTenantId": { "type": "string", "metadata": { "description": "Azure AD tenant ID. This will be needed when configuring managed exports." }, - "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2025-04-01').outputs.managedIdentityTenantId.value]" + "value": "[reference(resourceId('Microsoft.Resources/deployments', 'hub'), '2022-09-01').outputs.managedIdentityTenantId.value]" } } } \ No newline at end of file diff --git a/docs/deploy/finops-hub-latest.ui.json b/docs/deploy/finops-hub-latest.ui.json index 201aefd47..16b32e2ef 100644 --- a/docs/deploy/finops-hub-latest.ui.json +++ b/docs/deploy/finops-hub-latest.ui.json @@ -696,51 +696,6 @@ } ], "visible": true - }, - { - "name": "remoteHub", - "type": "Microsoft.Common.Section", - "label": "Remote hub configuration", - "elements": [ - { - "name": "remoteHubIntro", - "type": "Microsoft.Common.TextBlock", - "visible": true, - "options": { - "text": "Configure this hub to send data to a remote FinOps hub in another tenant or subscription. This enables cross-tenant cost management scenarios where a central tenant collects cost data from multiple tenants. Leave these fields empty if this is not a remote hub setup." - } - }, - { - "name": "remoteHubStorageUri", - "type": "Microsoft.Common.TextBox", - "label": "Remote hub storage URI", - "toolTip": "Data Lake storage endpoint from the remote hub storage account. Copy from the storage account Settings > Endpoints > Data Lake storage. Example: https://myremotehub.dfs.core.windows.net/", - "constraints": { - "required": false, - "regex": "^$|^https://.*\\.dfs\\.core\\.windows\\.net/?$", - "validationMessage": "Must be a valid Data Lake storage endpoint URL in the format: https://storageaccount.dfs.core.windows.net/" - }, - "visible": true - }, - { - "name": "remoteHubStorageKey", - "type": "Microsoft.Common.PasswordBox", - "label": { - "password": "Remote hub storage key" - }, - "toolTip": "Storage account access key for the remote hub. Copy from the remote hub storage account Security + networking > Access keys > key1/2 > Key.", - "constraints": { - "required": false, - "regex": "^$|^[A-Za-z0-9+/]{86}==$", - "validationMessage": "Must be a valid storage account access key (base64 encoded, ending with ==)" - }, - "options": { - "hideConfirmation": true - }, - "visible": true - } - ], - "visible": true } ] }, @@ -784,8 +739,6 @@ "ingestionRetentionInMonths": "[steps('retention').storage.ingestionMonths]", "dataExplorerRawRetentionInDays": "[steps('retention').dataExplorer.rawDays]", "dataExplorerFinalRetentionInMonths": "[steps('retention').dataExplorer.finalMonths]", - "remoteHubStorageUri": "[steps('advanced').remoteHub.remoteHubStorageUri]", - "remoteHubStorageKey": "[steps('advanced').remoteHub.remoteHubStorageKey]", "tagsByResource": "[steps('tags').tagsByResource]" } }